@joshuafolkken/kit 1.1784.0 → 1.2116.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 (690) hide show
  1. package/.claude/agents/reviewer-verification.md +19 -0
  2. package/.claude/agents/reviewer.md +19 -0
  3. package/.claude/settings.json +5 -6
  4. package/.claude/skills/dependency-update/SKILL.md +9 -10
  5. package/.claude/skills/workflow-commands/SKILL.md +6 -6
  6. package/.claude/skills/workflow-commands/background-commands.md +8 -5
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  13. package/.claude/skills/workflow-commands/backlogrun.md +1 -1
  14. package/.claude/skills/workflow-commands/chain-rule.md +41 -32
  15. package/.claude/skills/workflow-commands/delegation.md +6 -6
  16. package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
  17. package/.claude/skills/workflow-commands/followup.md +7 -5
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
  19. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  20. package/.claude/skills/workflow-commands/issue-comments.md +9 -4
  21. package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +2 -2
  23. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  24. package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
  25. package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
  26. package/.claude/skills/workflow-commands/prerequisite.md +3 -2
  27. package/.claude/skills/workflow-commands/retrospective.md +2 -2
  28. package/.claude/skills/workflow-commands/split-assessment.md +7 -5
  29. package/.codex/hooks.json +5 -5
  30. package/.github/actions/setup-pnpm/action.yml +9 -16
  31. package/.github/dependabot.yml +16 -0
  32. package/.github/workflows/auto-tag.yml +45 -0
  33. package/.github/workflows/ci.yml +155 -15
  34. package/.github/workflows/dependabot-auto-merge.yml +63 -4
  35. package/.github/workflows/production.yml +8 -0
  36. package/.github/workflows/publish.yml +60 -45
  37. package/.vscode/settings.json +19 -0
  38. package/CLAUDE.md +5 -12
  39. package/SECURITY.md +2 -2
  40. package/dist/CLAUDE.md +5 -12
  41. package/dist/commands/document-read-cli.js +2 -1
  42. package/dist/commands/document-section-main.js +2 -1
  43. package/dist/commands/read-set-cli.js +194 -134
  44. package/dist/commands/run-prep-main.js +9694 -10173
  45. package/dist/commands/run-step-cli.js +22138 -18285
  46. package/dist/hooks/audit-provision.js +23 -5
  47. package/dist/hooks/chunk-3GKE653K.js +6204 -0
  48. package/dist/hooks/chunk-45CJJOO7.js +1243 -0
  49. package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
  50. package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
  51. package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
  52. package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
  53. package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
  54. package/dist/hooks/chunk-VVNYJWEI.js +30 -0
  55. package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
  56. package/dist/hooks/codex-hook-adapter.js +8 -8
  57. package/dist/hooks/format-edited.js +4 -5
  58. package/dist/hooks/inputs.json +386 -360
  59. package/dist/hooks/pretool-guard.js +7 -6
  60. package/dist/hooks/session-lang.js +101 -258
  61. package/dist/hooks/stop-guard.js +1744 -2576
  62. package/dist/josh.js +7133 -6368
  63. package/dist/version/index.js +161 -1
  64. package/eslint/rules/comment-why-only.js +108 -0
  65. package/package.json +16 -9
  66. package/prompts/coding-standards.md +1 -1
  67. package/prompts/collaboration-workflow/file-edits.md +14 -11
  68. package/prompts/collaboration-workflow/issue-citation.md +0 -4
  69. package/prompts/collaboration-workflow/issue-template.md +40 -21
  70. package/prompts/collaboration-workflow/lane-instructions.md +3 -1
  71. package/prompts/collaboration-workflow/measurement.md +8 -1
  72. package/prompts/collaboration-workflow/operating-rules.md +11 -2
  73. package/prompts/collaboration-workflow/output-bounds.md +6 -2
  74. package/prompts/collaboration-workflow/plan-comment.md +1 -1
  75. package/prompts/collaboration-workflow/principles.md +9 -0
  76. package/prompts/collaboration-workflow/residency.md +3 -2
  77. package/prompts/collaboration-workflow/rule-delivery.md +13 -1
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  79. package/prompts/collaboration-workflow/wip-cap.md +3 -1
  80. package/prompts/review-rubric.md +8 -0
  81. package/prompts/review.md +31 -22
  82. package/prompts/testing-guide.md +1 -0
  83. package/scripts/agent/agent-argv.ts +14 -16
  84. package/scripts/agent/agent-provider.ts +138 -0
  85. package/scripts/agent/agent-role-profile.ts +108 -156
  86. package/scripts/agent/agent-safe-text.ts +20 -5
  87. package/scripts/agent/api-usage-limit.ts +65 -0
  88. package/scripts/agent/claude-agent-argv.ts +19 -9
  89. package/scripts/agent/codex-agent-argv.ts +118 -22
  90. package/scripts/agent/usage-limit-reset.ts +179 -0
  91. package/scripts/auth/claude-token-output.ts +2 -2
  92. package/scripts/auto-ok/auto-ok-author.ts +53 -0
  93. package/scripts/auto-ok/auto-ok-cli.ts +20 -9
  94. package/scripts/backlog/backlog-defect-priority.ts +1 -1
  95. package/scripts/backlog/backlog-drive-cli.ts +6 -1
  96. package/scripts/backlog/backlog-drive-hold.ts +3 -3
  97. package/scripts/backlog/backlog-drive-idle.ts +10 -0
  98. package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
  99. package/scripts/backlog/backlog-drive-launch.ts +8 -2
  100. package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
  101. package/scripts/backlog/backlog-drive-named.ts +28 -7
  102. package/scripts/backlog/backlog-drive-paused.ts +5 -4
  103. package/scripts/backlog/backlog-drive-restore.ts +11 -3
  104. package/scripts/backlog/backlog-drive.ts +21 -60
  105. package/scripts/backlog/backlog-gate-scope.ts +61 -0
  106. package/scripts/backlog/backlog-measure-close.ts +112 -0
  107. package/scripts/backlog/backlog-next.ts +39 -49
  108. package/scripts/backlog/backlog-occupied.ts +2 -2
  109. package/scripts/backlog/backlog-overlap.ts +42 -8
  110. package/scripts/backlog/backlog-plan-cli.ts +4 -1
  111. package/scripts/backlog/backlog-plan-comment.ts +81 -0
  112. package/scripts/backlog/backlog-plan-read.ts +19 -6
  113. package/scripts/backlog/backlog-pool.ts +50 -12
  114. package/scripts/backlog/backlog-rank.ts +60 -13
  115. package/scripts/backlog/backlog-stalled-detect.ts +2 -1
  116. package/scripts/backlog/backlog-waves.ts +17 -2
  117. package/scripts/backlog/backlogrun-launch.ts +7 -4
  118. package/scripts/cost-runtime/context-cut-payback.ts +27 -25
  119. package/scripts/cost-runtime/cost-cli.ts +37 -16
  120. package/scripts/cost-runtime/cost-pricing.ts +9 -0
  121. package/scripts/cost-runtime/cost-verdict.ts +4 -1
  122. package/scripts/delegation/delegation-policy.ts +2 -2
  123. package/scripts/delegation/duplicate-read-guard.ts +1 -0
  124. package/scripts/delegation/investigation-guard.ts +14 -2
  125. package/scripts/delegation/investigation-reads.ts +45 -23
  126. package/scripts/dependabot/dependabot-cooldown.ts +3 -2
  127. package/scripts/discovery/repo-party.ts +80 -20
  128. package/scripts/discovery/session-owner-pin.ts +51 -0
  129. package/scripts/document/capped-print.ts +1 -1
  130. package/scripts/document/command-reference-line-budget.ts +42 -0
  131. package/scripts/document/document-byte-budget.ts +17 -7
  132. package/scripts/document/document-checks.ts +20 -0
  133. package/scripts/document/document-ratchet-check.ts +101 -0
  134. package/scripts/document/document-section.ts +1 -0
  135. package/scripts/document/entry-read-budget.ts +7 -3
  136. package/scripts/document/issue-citation-budget.ts +94 -0
  137. package/scripts/epic/epic-cli-argv.ts +2 -2
  138. package/scripts/epic/epic-fetch.ts +4 -2
  139. package/scripts/epic/epic-graph.ts +7 -2
  140. package/scripts/epic/epic-issue.ts +3 -1
  141. package/scripts/epic/epic-rank.ts +5 -0
  142. package/scripts/exports/unused-members-check.ts +62 -0
  143. package/scripts/exports/unused-members-cli.ts +6 -51
  144. package/scripts/followup/followup-ci-path.ts +67 -0
  145. package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
  146. package/scripts/followup/followup-coderabbit-wait.ts +13 -10
  147. package/scripts/followup/followup-failed-tests.ts +160 -0
  148. package/scripts/followup/followup-job-needs.ts +100 -0
  149. package/scripts/followup/followup-job-rerun.ts +303 -0
  150. package/scripts/followup/followup-main-move.ts +113 -0
  151. package/scripts/followup/git-followup-finish.ts +43 -11
  152. package/scripts/followup/git-followup-pending.ts +3 -3
  153. package/scripts/followup/git-followup-stages.ts +41 -11
  154. package/scripts/followup/git-pr-followup-checks.ts +55 -16
  155. package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
  156. package/scripts/followup/git-pr-followup.ts +62 -27
  157. package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
  158. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
  159. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
  160. package/scripts/gate/busy-average.ts +34 -2
  161. package/scripts/gate/check-related-pinned.ts +43 -0
  162. package/scripts/gate/check-related-reuse.ts +64 -0
  163. package/scripts/gate/check-related-section.ts +57 -0
  164. package/scripts/gate/check-related-unplanned.ts +34 -0
  165. package/scripts/gate/check-related.ts +265 -0
  166. package/scripts/gate/command-memory.ts +1 -1
  167. package/scripts/gate/core-admission.ts +24 -6
  168. package/scripts/gate/core-budget-timing.ts +20 -6
  169. package/scripts/gate/core-budget.ts +40 -11
  170. package/scripts/gate/core-wait-record.ts +3 -2
  171. package/scripts/gate/core-wait.ts +25 -2
  172. package/scripts/gate/core-weights.ts +15 -7
  173. package/scripts/gate/gate-labels.ts +46 -0
  174. package/scripts/gate/gate-ledger.ts +14 -6
  175. package/scripts/gate/gate-plan.ts +103 -58
  176. package/scripts/gate/gate-report.ts +9 -7
  177. package/scripts/gate/gate-run-steps.ts +26 -3
  178. package/scripts/gate/gate-skip.ts +45 -6
  179. package/scripts/gate/gate-static-group.ts +60 -0
  180. package/scripts/gate/gate-steps.ts +2 -1
  181. package/scripts/gate/gate-tree.ts +4 -2
  182. package/scripts/gate/hook-gate-reuse.ts +19 -2
  183. package/scripts/gate/metrics-totals.ts +59 -0
  184. package/scripts/gate/scoped-green.ts +68 -15
  185. package/scripts/gate/since-green.ts +14 -6
  186. package/scripts/gate/structural-checks.ts +96 -0
  187. package/scripts/gate/verification-gate.ts +51 -16
  188. package/scripts/gate/verification-inputs.ts +21 -6
  189. package/scripts/gate/verification-tail.ts +52 -0
  190. package/scripts/gh/gh-author-association.ts +23 -2
  191. package/scripts/gh/git-ai-review-scan.ts +1 -1
  192. package/scripts/gh/git-gh-api-path.ts +12 -3
  193. package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
  194. package/scripts/gh/git-gh-exec.ts +32 -2
  195. package/scripts/gh/git-gh-issue-read.ts +4 -2
  196. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  197. package/scripts/gh/git-gh-issue-write.ts +8 -2
  198. package/scripts/gh/git-gh-issue.ts +12 -5
  199. package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
  200. package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
  201. package/scripts/gh/git-gh-pr-read.ts +74 -13
  202. package/scripts/gh/git-gh-pr-rest.ts +3 -1
  203. package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
  204. package/scripts/gh/git-gh-pr-too-large.ts +20 -0
  205. package/scripts/gh/git-gh-pr.ts +21 -7
  206. package/scripts/gh/git-gh-retry.ts +141 -0
  207. package/scripts/gh/git-pr-ai-review.ts +15 -6
  208. package/scripts/gh/git-pr-checks-eval.ts +3 -0
  209. package/scripts/gh/git-pr-checks.ts +5 -5
  210. package/scripts/git/base-tree.ts +30 -0
  211. package/scripts/git/change-base.ts +4 -10
  212. package/scripts/git/changed-paths-sync.ts +63 -0
  213. package/scripts/git/changed-paths.ts +16 -1
  214. package/scripts/git/git-common-directory.ts +11 -1
  215. package/scripts/git/git-diff-reads.ts +66 -20
  216. package/scripts/git/git-error.ts +9 -2
  217. package/scripts/git/git-schemas.ts +13 -0
  218. package/scripts/git/git-worktree.ts +13 -0
  219. package/scripts/git/gone-branch.ts +4 -1
  220. package/scripts/git/main-merge-budget-conflict.ts +89 -0
  221. package/scripts/git/main-merge-guard.ts +55 -18
  222. package/scripts/git/main-merge-regenerate.ts +59 -25
  223. package/scripts/git/main-merge.ts +53 -5
  224. package/scripts/git/no-base-checkout.ts +127 -0
  225. package/scripts/git/stash/git-stash.ts +11 -2
  226. package/scripts/git/stash/stash-push-cli.ts +10 -0
  227. package/scripts/hooks/batch-guard.ts +40 -21
  228. package/scripts/hooks/edited-cspell.ts +37 -11
  229. package/scripts/hooks/format-edited-file.ts +22 -2
  230. package/scripts/hooks/lane-plan-guard.ts +50 -0
  231. package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
  232. package/scripts/hooks/pre-commit-lint.ts +14 -1
  233. package/scripts/hooks/pre-push-related-switch.ts +10 -0
  234. package/scripts/hooks/pre-push-unit.ts +76 -10
  235. package/scripts/hooks/pretool-guard.ts +2 -0
  236. package/scripts/hooks/reserved-run.ts +14 -1
  237. package/scripts/hooks/run-hook.sh +23 -8
  238. package/scripts/hooks/step-zero-notice.ts +6 -1
  239. package/scripts/hooks/stop-actions.ts +3 -15
  240. package/scripts/hooks/stop-guard.ts +7 -6
  241. package/scripts/init/distributed-paths.ts +1 -0
  242. package/scripts/init/hook-command-rewrite.ts +5 -1
  243. package/scripts/init/hook-launch.ts +4 -2
  244. package/scripts/issue/aggregation-split.ts +52 -0
  245. package/scripts/issue/defect-origin-line.ts +19 -1
  246. package/scripts/issue/defect-rate-cli.ts +5 -102
  247. package/scripts/issue/defect-report.ts +4 -4
  248. package/scripts/issue/effect-audit-cli.ts +178 -0
  249. package/scripts/issue/effect-audit.ts +185 -0
  250. package/scripts/issue/expected-effect-lint.ts +64 -9
  251. package/scripts/issue/git-next-issues.ts +5 -0
  252. package/scripts/issue/issue-body-or-comment.ts +38 -4
  253. package/scripts/issue/issue-cite-cli.ts +1 -3
  254. package/scripts/issue/issue-classification.ts +1 -0
  255. package/scripts/issue/issue-closing-pr.ts +8 -1
  256. package/scripts/issue/issue-evidence-exit.ts +4 -2
  257. package/scripts/issue/issue-file-cap.ts +58 -0
  258. package/scripts/issue/issue-file-cli.ts +111 -38
  259. package/scripts/issue/issue-file-deferral.ts +52 -0
  260. package/scripts/issue/issue-file-fold.ts +29 -10
  261. package/scripts/issue/issue-file-interrupt.ts +85 -0
  262. package/scripts/issue/issue-file-lane-hint.ts +30 -0
  263. package/scripts/issue/issue-file.ts +43 -6
  264. package/scripts/issue/issue-labels.ts +19 -2
  265. package/scripts/issue/issue-lint-cli.ts +27 -2
  266. package/scripts/issue/issue-measured-cli.ts +13 -35
  267. package/scripts/issue/issue-measured-close.ts +58 -0
  268. package/scripts/issue/issue-not-before-label.ts +2 -2
  269. package/scripts/issue/issue-not-before.ts +4 -1
  270. package/scripts/issue/issue-priority-cli.ts +60 -0
  271. package/scripts/issue/issue-priority-raise.ts +55 -0
  272. package/scripts/issue/issue-priority.ts +174 -43
  273. package/scripts/issue/issue-rank.ts +21 -4
  274. package/scripts/issue/issue-read-cli.ts +32 -6
  275. package/scripts/issue/issue-read.ts +37 -12
  276. package/scripts/issue/issue-sample-gate.ts +95 -74
  277. package/scripts/issue/issue-sample-prime.ts +127 -0
  278. package/scripts/issue/issue-sample-required.ts +57 -0
  279. package/scripts/issue/issue-sample-source.ts +12 -11
  280. package/scripts/issue/issue-search.ts +124 -0
  281. package/scripts/issue/issue-state.ts +13 -1
  282. package/scripts/issue/issue-template-section.ts +43 -0
  283. package/scripts/issue/issue-watch-diff.ts +31 -1
  284. package/scripts/issue/issue-watch-hook.ts +11 -2
  285. package/scripts/issue/issue-watch.ts +55 -12
  286. package/scripts/issue/markdown-section.ts +1 -0
  287. package/scripts/issue/measure-declaration.ts +121 -0
  288. package/scripts/issue/measure-rerun-cli.ts +7 -70
  289. package/scripts/issue/plan-consumers-section.ts +22 -1
  290. package/scripts/issue/reproduction-measure.ts +3 -1
  291. package/scripts/issue/size-estimate.ts +90 -0
  292. package/scripts/issue/trusted-measure.ts +84 -0
  293. package/scripts/josh/command-usage.ts +3 -2
  294. package/scripts/josh/durable-state-migrate.ts +105 -0
  295. package/scripts/josh/durable-state.ts +112 -0
  296. package/scripts/josh/josh-aliases.ts +95 -0
  297. package/scripts/josh/josh-command-map.ts +1 -82
  298. package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
  299. package/scripts/josh/josh-commands-ai.ts +5 -5
  300. package/scripts/josh/josh-commands-development.ts +59 -58
  301. package/scripts/josh/josh-commands-issue-write.ts +23 -4
  302. package/scripts/josh/josh-commands-issue.ts +32 -0
  303. package/scripts/josh/josh-commands-lane.ts +87 -4
  304. package/scripts/josh/josh-commands-lint.ts +16 -3
  305. package/scripts/josh/josh-commands-maintenance.ts +15 -0
  306. package/scripts/josh/josh-commands-review.ts +41 -6
  307. package/scripts/josh/josh-commands-ship.ts +56 -8
  308. package/scripts/josh/josh-commands-test-check.ts +80 -0
  309. package/scripts/josh/josh-commands-workflow.ts +1 -1
  310. package/scripts/josh/josh-environment-file.ts +43 -3
  311. package/scripts/josh/josh-in-process.ts +2 -2
  312. package/scripts/josh/josh-logic.ts +5 -3
  313. package/scripts/josh/josh-run.ts +19 -1
  314. package/scripts/josh/run-record-path.ts +15 -0
  315. package/scripts/josh/stamp-file.ts +11 -2
  316. package/scripts/lane/lane-adopt-cli.ts +98 -0
  317. package/scripts/lane/lane-adopt.ts +208 -0
  318. package/scripts/lane/lane-await.ts +5 -8
  319. package/scripts/lane/lane-baseline.ts +123 -0
  320. package/scripts/lane/lane-breakdown-cli.ts +64 -0
  321. package/scripts/lane/lane-breakdown-implement.ts +170 -0
  322. package/scripts/lane/lane-breakdown-laps.ts +107 -0
  323. package/scripts/lane/lane-breakdown-pinned.ts +76 -0
  324. package/scripts/lane/lane-breakdown-text.ts +145 -0
  325. package/scripts/lane/lane-breakdown.ts +239 -0
  326. package/scripts/lane/lane-check-stats.ts +29 -8
  327. package/scripts/lane/lane-child-invocation.ts +9 -2
  328. package/scripts/lane/lane-child-mark-keys.ts +12 -0
  329. package/scripts/lane/lane-child-marker.ts +20 -1
  330. package/scripts/lane/lane-ci-path-stats.ts +73 -0
  331. package/scripts/lane/lane-claim-stats.ts +84 -0
  332. package/scripts/lane/lane-close.ts +8 -3
  333. package/scripts/lane/lane-dispatch-log.ts +3 -3
  334. package/scripts/lane/lane-dispatch.ts +2 -2
  335. package/scripts/lane/lane-gap-stats.ts +7 -6
  336. package/scripts/lane/lane-guard-policy.ts +7 -1
  337. package/scripts/lane/lane-implement-stats.ts +207 -0
  338. package/scripts/lane/lane-implement-subagents.ts +90 -0
  339. package/scripts/lane/lane-investigation-switch.ts +16 -0
  340. package/scripts/lane/lane-ledger-kind.ts +31 -0
  341. package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
  342. package/scripts/lane/lane-ledger-schema.ts +119 -36
  343. package/scripts/lane/lane-ledger-write.ts +257 -0
  344. package/scripts/lane/lane-ledger.ts +31 -177
  345. package/scripts/lane/lane-load.ts +1 -1
  346. package/scripts/lane/lane-measure-cli.ts +176 -59
  347. package/scripts/lane/lane-net-read.ts +44 -0
  348. package/scripts/lane/lane-net-time.ts +212 -0
  349. package/scripts/lane/lane-open.ts +16 -7
  350. package/scripts/lane/lane-output.ts +2 -4
  351. package/scripts/lane/lane-overrun-prompt.ts +147 -0
  352. package/scripts/lane/lane-overrun.ts +274 -0
  353. package/scripts/lane/lane-paths.ts +24 -1
  354. package/scripts/lane/lane-pause-cli.ts +4 -1
  355. package/scripts/lane/lane-paused-spans.ts +97 -0
  356. package/scripts/lane/lane-plan-check.ts +207 -0
  357. package/scripts/lane/lane-plan-cli.ts +41 -0
  358. package/scripts/lane/lane-plan.ts +144 -0
  359. package/scripts/lane/lane-registry.ts +14 -1
  360. package/scripts/lane/lane-relaunch.ts +1 -1
  361. package/scripts/lane/lane-round-one-stats.ts +51 -0
  362. package/scripts/lane/lane-sampler.ts +23 -4
  363. package/scripts/lane/lane-session-action.ts +93 -18
  364. package/scripts/lane/lane-session-broker.ts +49 -21
  365. package/scripts/lane/lane-session-reread-main.ts +14 -0
  366. package/scripts/lane/lane-session-reread.ts +51 -0
  367. package/scripts/lane/lane-ship-lap-stats.ts +74 -0
  368. package/scripts/lane/lane-sleep.ts +58 -0
  369. package/scripts/lane/lane-spent.ts +1 -1
  370. package/scripts/lane/lane-split-stats.ts +28 -0
  371. package/scripts/lane/lane-stage-stats.ts +13 -1
  372. package/scripts/lane/lane-state.ts +41 -9
  373. package/scripts/lane/lane-stats-tables.ts +58 -0
  374. package/scripts/lane/lane-stats.ts +21 -5
  375. package/scripts/lane/lane-stop-stats.ts +51 -19
  376. package/scripts/lane/lane-tail-stats.ts +32 -0
  377. package/scripts/lane/lane-transcript-calls.ts +67 -10
  378. package/scripts/lane/lane-transcript-stats.ts +44 -12
  379. package/scripts/lane/lane-value-cell.ts +72 -0
  380. package/scripts/lane/lane-value.ts +118 -0
  381. package/scripts/lane/lane-withdraw-closed.ts +124 -0
  382. package/scripts/lane/lane-withdraw.ts +108 -31
  383. package/scripts/lane/lane-yield.ts +85 -0
  384. package/scripts/lane/openai-lane-ship.ts +12 -11
  385. package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
  386. package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
  387. package/scripts/lane/openai-lane-supervisor.ts +5 -4
  388. package/scripts/lib/poll.ts +38 -9
  389. package/scripts/lib/settled-together.ts +10 -0
  390. package/scripts/lib/shell-quote.ts +21 -0
  391. package/scripts/lib/token-shape.ts +111 -0
  392. package/scripts/lint/eslint-daemon-sources.ts +79 -0
  393. package/scripts/lint/eslint-daemon.ts +45 -6
  394. package/scripts/lint/eslint-warm-cli.ts +7 -0
  395. package/scripts/lint/eslint-warm.ts +66 -0
  396. package/scripts/lint/import-graph.ts +141 -11
  397. package/scripts/lint/lint-gate-scope.ts +2 -2
  398. package/scripts/lint/lint-parallel.ts +54 -6
  399. package/scripts/lint/lint-related.ts +154 -33
  400. package/scripts/lint/rename-cli.ts +179 -0
  401. package/scripts/lint/rename-identifier.ts +192 -0
  402. package/scripts/lint/type-only-imports.ts +58 -0
  403. package/scripts/notify/telegram-notify.ts +12 -3
  404. package/scripts/observations/observation-record.ts +42 -16
  405. package/scripts/observations/observations-flush.ts +32 -9
  406. package/scripts/overrides/overrides-check.ts +9 -1
  407. package/scripts/release/release-approval-prune.ts +7 -1
  408. package/scripts/repo/required-checks-logic.ts +4 -1
  409. package/scripts/repo/ruleset-check.ts +4 -3
  410. package/scripts/report/markdown-shape.ts +28 -0
  411. package/scripts/report/report-format-reference.ts +15 -1
  412. package/scripts/report/report-labels.ts +12 -1
  413. package/scripts/report/report-lint-child-summary.ts +94 -0
  414. package/scripts/report/report-lint-cli.ts +26 -7
  415. package/scripts/report/report-lint.ts +2 -20
  416. package/scripts/review/prose-stale-cli.ts +88 -0
  417. package/scripts/review/prose-stale-scan.ts +152 -0
  418. package/scripts/review/review-attest.ts +73 -23
  419. package/scripts/review/review-brief-cli.ts +68 -19
  420. package/scripts/review/review-brief-paths.ts +22 -0
  421. package/scripts/review/review-brief.ts +14 -7
  422. package/scripts/review/review-comment-carry.ts +47 -0
  423. package/scripts/review/review-comment-round.ts +85 -22
  424. package/scripts/review/review-finding-ledger.ts +109 -7
  425. package/scripts/review/review-finding-text.ts +43 -0
  426. package/scripts/review/review-interaction.ts +11 -0
  427. package/scripts/review/review-lane-handover.ts +68 -0
  428. package/scripts/review/review-lint-cli.ts +57 -0
  429. package/scripts/review/review-lint-findings.ts +92 -0
  430. package/scripts/review/review-lint-sections.ts +80 -0
  431. package/scripts/review/review-lint.ts +221 -0
  432. package/scripts/review/review-prior-findings.ts +102 -0
  433. package/scripts/review/review-readers-scan.ts +4 -2
  434. package/scripts/review/review-record-cli.ts +177 -54
  435. package/scripts/review/review-record-code-round.ts +55 -0
  436. package/scripts/review/review-record-comment.ts +60 -0
  437. package/scripts/review/review-record-interaction.ts +35 -0
  438. package/scripts/review/review-record-pinned.ts +108 -0
  439. package/scripts/review/review-record.ts +25 -1
  440. package/scripts/review/review-reviewed-tree.ts +307 -0
  441. package/scripts/review/review-round-one-closure.ts +62 -0
  442. package/scripts/review/review-round-reuse.ts +106 -0
  443. package/scripts/review/review-round2-cli.ts +91 -29
  444. package/scripts/review/review-round2-skip-comment.ts +55 -0
  445. package/scripts/review/review-round2.ts +24 -8
  446. package/scripts/review/review-stamps.ts +7 -3
  447. package/scripts/review/review-stop-log.ts +20 -6
  448. package/scripts/review/review-stops-cli.ts +2 -1
  449. package/scripts/review/review-submit-cli.ts +101 -0
  450. package/scripts/rules/agent-model.ts +1 -1
  451. package/scripts/rules/bash-triggers.ts +3 -3
  452. package/scripts/rules/concise-reply.ts +88 -0
  453. package/scripts/rules/credential-body.ts +77 -0
  454. package/scripts/rules/decision-oracle.ts +1 -1
  455. package/scripts/rules/delivered-rules.ts +36 -17
  456. package/scripts/rules/destructive-command.ts +3 -11
  457. package/scripts/rules/direct-issue-comment.ts +34 -12
  458. package/scripts/rules/direct-issue-read.ts +191 -0
  459. package/scripts/rules/direct-pr-create.ts +4 -24
  460. package/scripts/rules/early-heartbeat.ts +2 -0
  461. package/scripts/rules/edit-tool.ts +27 -0
  462. package/scripts/rules/file-edit-rows.ts +17 -0
  463. package/scripts/rules/file-rewrite.ts +146 -0
  464. package/scripts/rules/firing-point.ts +3 -2
  465. package/scripts/rules/gh-api.ts +128 -7
  466. package/scripts/rules/gh-repo-selector.ts +84 -0
  467. package/scripts/rules/git-force.ts +2 -2
  468. package/scripts/rules/guard-remedy.ts +14 -5
  469. package/scripts/rules/implementation-cut.ts +61 -28
  470. package/scripts/rules/investigator-foreground.ts +231 -0
  471. package/scripts/rules/issue-citation.ts +15 -60
  472. package/scripts/rules/issue-comments.ts +9 -4
  473. package/scripts/rules/josh-git-bare.ts +1 -1
  474. package/scripts/rules/lane-background.ts +41 -13
  475. package/scripts/rules/lane-direct-tool.ts +107 -9
  476. package/scripts/rules/lane-interactive-ask.ts +4 -1
  477. package/scripts/rules/lane-park.ts +4 -2
  478. package/scripts/rules/lane-spilled-read.ts +90 -0
  479. package/scripts/rules/lane-split-park.ts +1 -1
  480. package/scripts/rules/lane-switch-main.ts +4 -4
  481. package/scripts/rules/last-prompt.ts +20 -47
  482. package/scripts/rules/oracle-consulted.ts +1 -1
  483. package/scripts/rules/permission-guards.ts +5 -1
  484. package/scripts/rules/piped-verification-shapes.ts +110 -0
  485. package/scripts/rules/piped-verification.ts +67 -17
  486. package/scripts/rules/poll-loop.ts +72 -9
  487. package/scripts/rules/pr-merge.ts +5 -5
  488. package/scripts/rules/pre-gate-cut.ts +3 -3
  489. package/scripts/rules/protected-files.ts +88 -24
  490. package/scripts/rules/reply-language.ts +3 -17
  491. package/scripts/rules/reply-prose.ts +34 -0
  492. package/scripts/rules/repo-admin.ts +132 -0
  493. package/scripts/rules/reviewer-foreground.ts +88 -0
  494. package/scripts/rules/round-one-check-related.ts +120 -0
  495. package/scripts/rules/rule-body-guard.ts +5 -15
  496. package/scripts/rules/rule-value-cache.ts +53 -5
  497. package/scripts/rules/rule-value-candidates.ts +84 -0
  498. package/scripts/rules/rule-value-cli.ts +59 -11
  499. package/scripts/rules/rule-value.ts +17 -4
  500. package/scripts/rules/run-tail-rule.ts +1 -1
  501. package/scripts/rules/sed-in-place.ts +196 -0
  502. package/scripts/rules/shell-segments.ts +10 -2
  503. package/scripts/rules/stop-rule-rows.ts +1 -13
  504. package/scripts/rules/stop-rules.ts +33 -58
  505. package/scripts/rules/test-declared-commit.ts +1 -1
  506. package/scripts/rules/third-party-write.ts +110 -23
  507. package/scripts/rules/timed-rerun.ts +1 -1
  508. package/scripts/rules/turn-filings.ts +90 -0
  509. package/scripts/rules/worktree-guard.ts +22 -7
  510. package/scripts/run/board/run-board-ending-reason.ts +6 -2
  511. package/scripts/run/board/run-board-fit.ts +24 -1
  512. package/scripts/run/board/run-board-found.ts +134 -0
  513. package/scripts/run/board/run-board-header.ts +8 -0
  514. package/scripts/run/board/run-board-labels.ts +11 -1
  515. package/scripts/run/board/run-board-lane-time.ts +1 -0
  516. package/scripts/run/board/run-board-link.ts +15 -2
  517. package/scripts/run/board/run-board-notes.ts +7 -1
  518. package/scripts/run/board/run-board-operation.ts +4 -2
  519. package/scripts/run/board/run-board-phase.ts +2 -5
  520. package/scripts/run/board/run-board-plan.ts +1 -1
  521. package/scripts/run/board/run-board-read.ts +46 -1
  522. package/scripts/run/board/run-board-reason.ts +20 -8
  523. package/scripts/run/board/run-board-render-checks.ts +3 -2
  524. package/scripts/run/board/run-board-render-drawn.ts +11 -0
  525. package/scripts/run/board/run-board-render-found.ts +157 -0
  526. package/scripts/run/board/run-board-render-lead.ts +9 -0
  527. package/scripts/run/board/run-board-render-legend.ts +19 -0
  528. package/scripts/run/board/run-board-render-tail.ts +1 -1
  529. package/scripts/run/board/run-board-render.ts +82 -18
  530. package/scripts/run/board/run-board-spin.ts +1 -3
  531. package/scripts/run/board/run-board-status.ts +2 -0
  532. package/scripts/run/board/run-board-tick.ts +39 -10
  533. package/scripts/run/carry/run-carry-ended.ts +3 -2
  534. package/scripts/run/carry/run-carry.ts +3 -2
  535. package/scripts/run/cut/run-cut-cli.ts +33 -133
  536. package/scripts/run/cut/run-cut-digest.ts +152 -0
  537. package/scripts/run/cut/run-cut-handoff.ts +52 -8
  538. package/scripts/run/cut/run-cut-report.ts +24 -3
  539. package/scripts/run/cut/run-cut-resume.ts +129 -0
  540. package/scripts/run/cut/run-cut-review.ts +105 -0
  541. package/scripts/run/cut/run-cut.ts +18 -7
  542. package/scripts/run/detached-launch.ts +1 -1
  543. package/scripts/run/entry/run-entry-cli.ts +88 -19
  544. package/scripts/run/entry/run-entry-spill.ts +83 -0
  545. package/scripts/run/entry/run-entry-stop.ts +5 -1
  546. package/scripts/run/entry/run-entry-trust.ts +91 -0
  547. package/scripts/run/event/run-event-baseline.ts +56 -0
  548. package/scripts/run/event/run-event-filed.ts +9 -3
  549. package/scripts/run/event/run-event-overrun.ts +197 -0
  550. package/scripts/run/event/run-event-render.ts +4 -0
  551. package/scripts/run/event/run-event-stream.ts +25 -3
  552. package/scripts/run/hold/run-stop-notify.ts +3 -8
  553. package/scripts/run/merge/run-merge-cap.ts +57 -0
  554. package/scripts/run/merge/run-merge-cli.ts +66 -23
  555. package/scripts/run/merge/run-merge-outcome.ts +2 -0
  556. package/scripts/run/merge/run-merge-ship.ts +13 -3
  557. package/scripts/run/merge/run-merge-signals.ts +40 -23
  558. package/scripts/run/merge/run-merge-steps.ts +56 -36
  559. package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
  560. package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
  561. package/scripts/run/merge/run-merge.ts +41 -14
  562. package/scripts/run/review/run-review-cli.ts +306 -20
  563. package/scripts/run/review/run-review-early-push.ts +121 -0
  564. package/scripts/run/review/run-review-join-read.ts +94 -0
  565. package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
  566. package/scripts/run/review/run-review-pinned-join.ts +216 -0
  567. package/scripts/run/review/run-review-pinned-steps.ts +383 -0
  568. package/scripts/run/review/run-review-pinned-tree.ts +37 -0
  569. package/scripts/run/review/run-review-pinned.ts +53 -0
  570. package/scripts/run/review/run-review-steps.ts +70 -34
  571. package/scripts/run/review/run-review.ts +26 -6
  572. package/scripts/run/run-parent-cut-hook.ts +3 -4
  573. package/scripts/run/run-prep.ts +1 -0
  574. package/scripts/run/run-stage-read.ts +2 -6
  575. package/scripts/run/run-step-cli.ts +17 -5
  576. package/scripts/run/run-step-lane-plan.ts +27 -0
  577. package/scripts/run/run-step-section.ts +26 -7
  578. package/scripts/run/run-step.ts +5 -2
  579. package/scripts/run/ship/run-ship-args.ts +19 -0
  580. package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
  581. package/scripts/run/ship/run-ship-cli.ts +131 -39
  582. package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
  583. package/scripts/run/ship/run-ship-detach.ts +10 -4
  584. package/scripts/run/ship/run-ship-handover.ts +106 -0
  585. package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
  586. package/scripts/run/ship/run-ship-lap.ts +63 -0
  587. package/scripts/run/ship/run-ship-next.ts +27 -26
  588. package/scripts/run/ship/run-ship-overlap.ts +8 -3
  589. package/scripts/run/ship/run-ship-place.ts +9 -2
  590. package/scripts/run/ship/run-ship-planned.ts +92 -0
  591. package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
  592. package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
  593. package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
  594. package/scripts/run/ship/run-ship-preflight.ts +16 -4
  595. package/scripts/run/ship/run-ship-probe.ts +23 -9
  596. package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
  597. package/scripts/run/ship/run-ship-relaunch.ts +51 -0
  598. package/scripts/run/ship/run-ship-return.ts +103 -29
  599. package/scripts/run/ship/run-ship-review-reason.ts +24 -0
  600. package/scripts/run/ship/run-ship-review-steps.ts +230 -59
  601. package/scripts/run/ship/run-ship-review.ts +171 -51
  602. package/scripts/run/ship/run-ship-reviewer.ts +138 -42
  603. package/scripts/run/ship/run-ship-scoped.ts +39 -10
  604. package/scripts/run/ship/run-ship-stage.ts +49 -13
  605. package/scripts/run/ship/run-ship-steps.ts +24 -4
  606. package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
  607. package/scripts/run/ship/run-ship-stop-context.ts +50 -13
  608. package/scripts/run/ship/run-ship-sync.ts +171 -30
  609. package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
  610. package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
  611. package/scripts/run/ship/run-ship-waiter.ts +114 -0
  612. package/scripts/run/ship/run-ship.ts +16 -2
  613. package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
  614. package/scripts/run/wake/run-wake-cli.ts +6 -113
  615. package/scripts/run/wake/run-wake-code.ts +53 -11
  616. package/scripts/run/wake/run-wake-driver.ts +5 -3
  617. package/scripts/run/wake/run-wake-finish.ts +137 -0
  618. package/scripts/run/wake/run-wake-loop.ts +5 -3
  619. package/scripts/run/wake/run-wake-ports.ts +11 -3
  620. package/scripts/run/wake/run-wake-session.ts +2 -0
  621. package/scripts/run/wake/run-wake.ts +3 -2
  622. package/scripts/safe-chain/ci-installer-pin.ts +49 -8
  623. package/scripts/safe-chain/project-config.ts +3 -2
  624. package/scripts/security/secretlint-scan.ts +42 -9
  625. package/scripts/security/security-audit-provision-main.ts +9 -1
  626. package/scripts/security/security-audit-provision.ts +1 -1
  627. package/scripts/security/security-audit.ts +21 -1
  628. package/scripts/security/sonar-file.ts +10 -5
  629. package/scripts/split/split-assess.ts +12 -3
  630. package/scripts/split/split-early-notice.ts +116 -0
  631. package/scripts/split/split-measure.ts +20 -2
  632. package/scripts/split/split-resolution.ts +107 -0
  633. package/scripts/test/direct-importers.ts +185 -0
  634. package/scripts/test/pure-files-entries.ts +83 -0
  635. package/scripts/test/related-heavy-tests.ts +81 -0
  636. package/scripts/test/test-no-base.ts +88 -0
  637. package/scripts/test/test-pinned-cache-logic.ts +363 -0
  638. package/scripts/test/test-pinned-cache.ts +337 -0
  639. package/scripts/test/test-pinned-emit.ts +49 -0
  640. package/scripts/test/test-pinned-functions.ts +116 -0
  641. package/scripts/test/test-pinned-ledger.ts +80 -0
  642. package/scripts/test/test-pinned-logic.ts +208 -20
  643. package/scripts/test/test-pinned-main.ts +84 -18
  644. package/scripts/test/test-pinned-pool.ts +73 -0
  645. package/scripts/test/test-pinned-probe.ts +254 -0
  646. package/scripts/test/test-pinned-prune-logic.ts +118 -0
  647. package/scripts/test/test-pinned-reach.ts +194 -0
  648. package/scripts/test/test-pinned-record.ts +196 -0
  649. package/scripts/test/test-pinned-rewire.ts +148 -0
  650. package/scripts/test/test-pinned-spawn.ts +81 -0
  651. package/scripts/test/test-pinned-stage.ts +70 -0
  652. package/scripts/test/test-pinned.ts +258 -47
  653. package/scripts/test/test-reach-cli.ts +61 -0
  654. package/scripts/test/test-read-map.ts +35 -11
  655. package/scripts/test/test-read-recording.ts +17 -5
  656. package/scripts/test/test-read-trace.ts +26 -8
  657. package/scripts/test/test-red-logic.ts +46 -7
  658. package/scripts/test/test-red.ts +34 -14
  659. package/scripts/test/test-related-claim.ts +37 -0
  660. package/scripts/test/test-related-main.ts +4 -1
  661. package/scripts/test/test-related-moved.ts +91 -0
  662. package/scripts/test/test-related-record.ts +43 -23
  663. package/scripts/test/test-related-scope.ts +99 -34
  664. package/scripts/test/test-related-synced.ts +135 -0
  665. package/scripts/test/test-related-touched.ts +84 -0
  666. package/scripts/test/test-related.ts +167 -83
  667. package/scripts/test/test-unit-guard.ts +28 -5
  668. package/scripts/test/unit-durations.ts +41 -0
  669. package/scripts/test/unit-worker-share.ts +5 -4
  670. package/scripts/test/vitest-cache-directory.ts +19 -0
  671. package/scripts/test/vitest-cli-excludes.ts +14 -0
  672. package/scripts/test/vitest-failure-summary.ts +111 -0
  673. package/scripts/test/whole-suite-files.ts +12 -1
  674. package/scripts/time-runtime/time-batch-guard.ts +42 -4
  675. package/scripts/time-runtime/time-bundle-call.ts +1 -1
  676. package/scripts/time-runtime/time-bundles.ts +36 -5
  677. package/scripts/time-runtime/time-markers.ts +2 -2
  678. package/scripts/time-runtime/time-search-call.ts +38 -0
  679. package/scripts/time-runtime/time-shell.ts +1 -1
  680. package/scripts/time-runtime/time-transcript-line.ts +17 -5
  681. package/scripts/version/development-engines-verdict.ts +124 -0
  682. package/scripts/version/latest-corepack.ts +2 -0
  683. package/templates/gitignore +2 -0
  684. package/templates/workflows/ci.yml +41 -0
  685. package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
  686. package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
  687. package/dist/hooks/chunk-TUIFS5E7.js +0 -72
  688. package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
  689. package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
  690. package/scripts/rules/filing-cap.ts +0 -139
@@ -1,64 +1,89 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
1
3
  import { SUITE_TIMEOUT_MS } from '#scripts/lib/timeouts'
2
4
  import { execa } from 'execa'
3
5
  import { git_command } from './git-command'
4
6
  import { git_spawn } from './git-spawn'
7
+ import { main_merge_budget_conflict } from './main-merge-budget-conflict'
5
8
 
6
- // A merge of the default branch that stops only on generated files is finished here rather than by a
7
- // resumed session: each such file is the output of a command, so running that command on the merged
8
- // tree writes the one correct resolution, with no conflict marker for anyone to read.
9
+ // A merge of the default branch that stops only on the files below is finished here rather than by a
10
+ // resumed session, with no conflict marker for anyone to read. A generated file is the output of a
11
+ // command, so running that command on the merged tree writes the one correct resolution. A rewritten
12
+ // file is resolved by a rule that keeps both sides of the merge, and throws on any hunk it cannot.
9
13
  //
10
- // **This table is the single list of what is resolved that way** — a file a person writes never
11
- // belongs in it, because regenerating would discard that person's side of the merge. A conflict on any
12
- // path not listed here still stops as `conflict`. A file belongs here only once its committed content
13
- // is exactly what its command writes: `tools/test/pure-files.ts` is not, so regenerating it would
14
- // rewrite far more than the conflict.
14
+ // **This table is the single list of what is resolved that way** — a file a person writes belongs in
15
+ // it only with a rewrite rule, because regenerating would discard that person's side of the merge. A
16
+ // conflict on any path not listed here still stops as `conflict`. A file is regenerated only once its
17
+ // committed content is exactly what its command writes: `tools/test/pure-files.ts` is not, so
18
+ // regenerating it would rewrite far more than the conflict.
15
19
 
16
20
  interface RegeneratedFile {
17
21
  path: string
18
22
  command: ReadonlyArray<string>
19
23
  }
20
24
 
21
- const REGENERATED_FILES: ReadonlyArray<RegeneratedFile> = [
25
+ interface RewrittenFile {
26
+ path: string
27
+ rewrite: (content: string) => string
28
+ }
29
+
30
+ type ResolvedFile = RegeneratedFile | RewrittenFile
31
+
32
+ const RESOLVED_FILES: ReadonlyArray<ResolvedFile> = [
22
33
  {
23
34
  path: 'docs/josh-command-catalog.md',
24
- command: ['pnpm', 'exec', 'tsx', 'tools/document/generate-catalog.ts'],
35
+ command: ['pnpm', 'exec', 'tsx', 'tools/document/generate-catalog-main.ts'],
36
+ },
37
+ {
38
+ path: main_merge_budget_conflict.BUDGET_TABLE_PATH,
39
+ rewrite: main_merge_budget_conflict.resolve,
25
40
  },
26
41
  ]
27
42
 
28
- function regenerated_for(
43
+ function resolved_for(
29
44
  files: ReadonlyArray<string>,
30
- table: ReadonlyArray<RegeneratedFile>,
31
- ): ReadonlyArray<RegeneratedFile> {
45
+ table: ReadonlyArray<ResolvedFile>,
46
+ ): ReadonlyArray<ResolvedFile> {
32
47
  const entries = files.map((file) => table.find((entry) => entry.path === file))
33
48
 
34
49
  return entries.every((entry) => entry !== undefined) ? entries : []
35
50
  }
36
51
 
37
- // Each generator writes only its own file, so they run side by side.
38
- async function regenerate(entries: ReadonlyArray<RegeneratedFile>, cwd: string): Promise<void> {
52
+ async function rewrite_file(entry: RewrittenFile, cwd: string): Promise<void> {
53
+ const file = path.join(cwd, entry.path)
54
+
55
+ await writeFile(file, entry.rewrite(await readFile(file, 'utf8')))
56
+ }
57
+
58
+ async function regenerate_file(entry: RegeneratedFile, cwd: string): Promise<void> {
59
+ const [binary = '', ...command_arguments] = entry.command
60
+
61
+ await execa(binary, command_arguments, { cwd })
62
+ }
63
+
64
+ // Each entry writes only its own file, so they run side by side.
65
+ async function regenerate(entries: ReadonlyArray<ResolvedFile>, cwd: string): Promise<void> {
39
66
  await Promise.all(
40
67
  entries.map(async (entry) => {
41
- const [binary = '', ...command_arguments] = entry.command
42
-
43
- await execa(binary, command_arguments, { cwd })
68
+ await ('rewrite' in entry ? rewrite_file(entry, cwd) : regenerate_file(entry, cwd))
44
69
  }),
45
70
  )
46
71
  }
47
72
 
48
73
  // `--no-edit` keeps the message the merge was started with — the one carrying the issue number the
49
74
  // `commit-msg` hook asks for.
50
- async function conclude_merge(entries: ReadonlyArray<RegeneratedFile>): Promise<void> {
75
+ async function conclude_merge(entries: ReadonlyArray<ResolvedFile>): Promise<void> {
51
76
  await git_spawn.with_output('add', ['--', ...entries.map((entry) => entry.path)])
52
77
  await git_spawn.with_output('commit', ['--no-edit'], { timeout_ms: SUITE_TIMEOUT_MS })
53
78
  }
54
79
 
55
- // Whether the merge that stopped on `files` was finished. A command that fails leaves the merge in
56
- // progress and answers `false`, so the stop is the `conflict` it would have been without this table.
80
+ // Whether the merge that stopped on `files` was finished. A command that fails, or a rewrite that
81
+ // refuses a hunk, leaves the merge in progress and answers `false`, so the stop is the `conflict` it would have been without this table.
57
82
  async function resolve(
58
83
  files: ReadonlyArray<string>,
59
- table: ReadonlyArray<RegeneratedFile> = REGENERATED_FILES,
84
+ table: ReadonlyArray<ResolvedFile> = RESOLVED_FILES,
60
85
  ): Promise<boolean> {
61
- const entries = regenerated_for(files, table)
86
+ const entries = resolved_for(files, table)
62
87
 
63
88
  if (entries.length === 0) return false
64
89
 
@@ -72,7 +97,16 @@ async function resolve(
72
97
  return true
73
98
  }
74
99
 
75
- const main_merge_regenerate = { REGENERATED_FILES, resolve }
100
+ // Whether a merge stopping on exactly `files` is one `resolve` would try to finish — asked before any
101
+ // merge, by `josh ship`'s conflict probe, so it never stops on a conflict the sync resolves.
102
+ function is_resolvable(
103
+ files: ReadonlyArray<string>,
104
+ table: ReadonlyArray<ResolvedFile> = RESOLVED_FILES,
105
+ ): boolean {
106
+ return resolved_for(files, table).length > 0
107
+ }
108
+
109
+ const main_merge_regenerate = { RESOLVED_FILES, is_resolvable, resolve }
76
110
 
77
- export type { RegeneratedFile }
111
+ export type { RegeneratedFile, ResolvedFile, RewrittenFile }
78
112
  export { main_merge_regenerate }
@@ -1,8 +1,10 @@
1
1
  import { extract_issue_number } from '#scripts/hooks/check-commit-message'
2
2
  import { issue_cite } from '#scripts/issue/issue-cite'
3
3
  import { composite_arguments, USAGE_ERROR_EXIT_CODE } from '#scripts/josh/josh-composite-arguments'
4
+ import { lane_install } from '#scripts/lane/lane-install'
4
5
  import { error_text } from '#scripts/lib/error-message'
5
6
  import { split_assess } from '#scripts/split/split-assess'
7
+ import { split_early_notice } from '#scripts/split/split-early-notice'
6
8
  import { split_measure } from '#scripts/split/split-measure'
7
9
  import { git_command } from './git-command'
8
10
  import { main_merge_guard } from './main-merge-guard'
@@ -33,6 +35,9 @@ const FAILURE_EXIT_CODE = 1
33
35
  const COMMAND_NAME = 'main:merge'
34
36
  const SPLIT_UNMEASURED =
35
37
  'split notice: the diff could not be measured — run `pnpm josh split:assess`'
38
+ const LOCKFILE = 'pnpm-lock.yaml'
39
+ const INSTALL_OWED =
40
+ 'the merge brought in a lockfile change, but installing it failed — run `pnpm install --frozen-lockfile`'
36
41
 
37
42
  // git's default merge message carries no `#N`, so on an issue branch the `commit-msg` hook refuses
38
43
  // it and leaves the index mid-merge. The number is read with the hook's
@@ -47,7 +52,7 @@ function merge_message(default_branch: string, current_branch: string): string |
47
52
  }
48
53
 
49
54
  // What one merge of the default branch came to. `josh ship` merges it again
50
- // before the commit and on a conflicting pull request, and branches on the answer rather than on an
55
+ // before the gate, before the push and on a followup failed on its checks, and branches on the answer rather than on an
51
56
  // exit code: `current` brought nothing in, so the tree is unchanged; `merged` changed it; `refused`
52
57
  // is the guard's answer, nothing attempted; `conflict` names the paths git left unmerged, so a resumed
53
58
  // session goes straight to them instead of asking git again.
@@ -65,7 +70,7 @@ interface MergeTarget {
65
70
 
66
71
  // A failure that left no unmerged path — a remote that cannot be reached, a hook that refused — is not
67
72
  // a conflict, so it is rethrown rather than reported as one with nothing to resolve. A conflict only on
68
- // generated files is regenerated and committed (`main-merge-regenerate.ts`), so it answers `undefined`
73
+ // the files `main-merge-regenerate.ts` resolves is resolved and committed, so it answers `undefined`
69
74
  // and the merge reads as finished.
70
75
  async function unresolved_conflict(error: unknown): Promise<MergeOutcome | undefined> {
71
76
  const files = main_merge_guard.unmerged_paths(await git_command.status())
@@ -89,6 +94,32 @@ async function merge_into(target: MergeTarget): Promise<MergeOutcome> {
89
94
  return { kind: target.is_current ? 'current' : 'merged', branch: default_branch }
90
95
  }
91
96
 
97
+ // joshuafolkken/kit#4777: a merge that brings in a lockfile change leaves `node_modules` on the old
98
+ // one, so the next check fails on files this branch never touched. Only a finished merge installs — a
99
+ // conflict or a refusal changed no dependency yet. A failed install does not fail the merge; it says
100
+ // the install is still owed, with pnpm's output. A throw is caught for the same reason: the merge is
101
+ // already committed, so `josh ship` must not read it as refused.
102
+ async function install_failure(): Promise<string | undefined> {
103
+ try {
104
+ const result = await lane_install.install_dependencies(await git_command.repository_root())
105
+
106
+ return result.is_installed ? undefined : result.output
107
+ } catch (error) {
108
+ return error_text.message_of(error)
109
+ }
110
+ }
111
+
112
+ async function install_merged_lockfile(
113
+ outcome: MergeOutcome,
114
+ incoming: ReadonlyArray<string>,
115
+ ): Promise<void> {
116
+ if (outcome.kind !== 'merged' || !incoming.includes(LOCKFILE)) return
117
+
118
+ const failure = await install_failure()
119
+
120
+ if (failure !== undefined) console.warn(`${INSTALL_OWED}\n${failure}`)
121
+ }
122
+
92
123
  // The fetch is what makes `origin/<default>` current before the merge reads it. `git pull` did the
93
124
  // two together, which is the only thing it was doing here that is worth keeping. The refusal is read
94
125
  // after the fetch, so the incoming paths are the ones the merge would bring in.
@@ -104,14 +135,22 @@ async function merge(): Promise<MergeOutcome> {
104
135
 
105
136
  if (message !== undefined) return { kind: 'refused', message }
106
137
 
107
- return await merge_into({ default_branch, current_branch, is_current: incoming.length === 0 })
138
+ const outcome = await merge_into({
139
+ default_branch,
140
+ current_branch,
141
+ is_current: incoming.length === 0,
142
+ })
143
+
144
+ await install_merged_lockfile(outcome, incoming)
145
+
146
+ return outcome
108
147
  }
109
148
 
110
149
  // A merge that stops on a conflict keeps git's report and adds how to finish it, so the next step is
111
150
  // the sanctioned commit rather than a request for `git add`.
112
151
  function failure_text(outcome: MergeOutcome): string | undefined {
113
152
  if (outcome.kind === 'refused') return outcome.message
114
- if (outcome.kind === 'conflict') return main_merge_guard.CONFLICT_HINT
153
+ if (outcome.kind === 'conflict') return main_merge_guard.conflict_hint()
115
154
 
116
155
  return undefined
117
156
  }
@@ -121,9 +160,18 @@ function failure_text(outcome: MergeOutcome): string | undefined {
121
160
  // advisory: a finished merge never fails on it, and an unreadable diff says so rather than reading as
122
161
  // under the guide. It sits on the command, not on `merge()`, because `josh ship` merges again past the
123
162
  // point a run can act on it.
163
+ async function measured_notice(): Promise<string | undefined> {
164
+ const measurement = await split_measure.read_branch()
165
+ const notice = split_assess.notice(measurement)
166
+
167
+ if (notice !== undefined) await split_early_notice.record(measurement)
168
+
169
+ return notice
170
+ }
171
+
124
172
  async function split_notice(): Promise<string | undefined> {
125
173
  try {
126
- return split_assess.notice(await split_measure.read_branch())
174
+ return await measured_notice()
127
175
  } catch (error) {
128
176
  return `${SPLIT_UNMEASURED}: ${error_text.message_of(error)}`
129
177
  }
@@ -0,0 +1,127 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ mkdtempSync,
6
+ rmSync,
7
+ statSync,
8
+ symlinkSync,
9
+ writeFileSync,
10
+ } from 'node:fs'
11
+ import { tmpdir } from 'node:os'
12
+ import path from 'node:path'
13
+ import { repo_discovery } from '#scripts/discovery/repo-discovery'
14
+ import { execa } from 'execa'
15
+ import { changed_paths } from './changed-paths'
16
+ import { git_command } from './git-command'
17
+ import { git_location_environment } from './git-location-environment'
18
+
19
+ // **A checkout of this tree as CI's `actions/checkout` hands it over** — a detached, depth-one `HEAD`
20
+ // with no local default branch and no remote-tracking ref (joshuafolkken/kit#4710). Code that passes
21
+ // `main` to git reads the local branch every lane has and passes here, then fails in CI with
22
+ // `bad revision 'main'`; run inside this checkout, it fails here too. **The `origin` remote is kept**,
23
+ // as CI keeps it: code that reads the repository's owner from it reads it here too
24
+ // (joshuafolkken/kit#4738).
25
+ //
26
+ // **No object is copied**: the objects are borrowed through `objects/info/alternates`, and only the
27
+ // tree is checked out. The work in progress is copied over it, and `node_modules` is linked, so the
28
+ // tests read what the lane's own run reads.
29
+
30
+ const CHECKOUT_PREFIX = 'josh-no-base-'
31
+ const NODE_MODULES = 'node_modules'
32
+ const ORIGIN_URL_KEY = 'remote.origin.url'
33
+
34
+ // Cleared rather than inherited: under a git hook `GIT_DIR` beats `cwd`, and every command here must
35
+ // act on the new checkout.
36
+ async function git(cwd: string, arguments_: ReadonlyArray<string>): Promise<string> {
37
+ const { stdout } = await execa('git', arguments_, {
38
+ cwd,
39
+ env: git_location_environment.location_free_environment(),
40
+ extendEnv: true,
41
+ })
42
+
43
+ return stdout.trim()
44
+ }
45
+
46
+ // A file the work deleted is deleted from the checkout too. A directory git names as one path — a
47
+ // nested repository, a submodule — is not the work's to copy.
48
+ function copy_work(root: string, directory: string, file: string): void {
49
+ const source = path.join(root, file)
50
+ const target = path.join(directory, file)
51
+
52
+ if (!existsSync(source)) {
53
+ rmSync(target, { force: true })
54
+
55
+ return
56
+ }
57
+
58
+ if (statSync(source).isDirectory()) return
59
+
60
+ mkdirSync(path.dirname(target), { recursive: true })
61
+ copyFileSync(source, target)
62
+ }
63
+
64
+ async function borrow_objects(root: string, directory: string): Promise<void> {
65
+ const common = await git(root, ['rev-parse', '--path-format=absolute', '--git-common-dir'])
66
+ const alternates = path.join(directory, '.git', 'objects', 'info', 'alternates')
67
+
68
+ writeFileSync(alternates, `${path.join(common, 'objects')}\n`)
69
+ }
70
+
71
+ // The remote is declared and never fetched, so it names the repository without bringing a
72
+ // remote-tracking ref along. A repository with no `origin` hands none over. Written with `git config`
73
+ // rather than `git remote add`, which the unit suite's network guard refuses whole.
74
+ async function add_origin(root: string, directory: string): Promise<void> {
75
+ const origin_url = repo_discovery.read_origin_url(root)
76
+
77
+ if (origin_url !== undefined) await git(directory, ['config', ORIGIN_URL_KEY, origin_url])
78
+ }
79
+
80
+ function link_dependencies(root: string, directory: string): void {
81
+ const source = path.join(root, NODE_MODULES)
82
+
83
+ if (existsSync(source)) symlinkSync(source, path.join(directory, NODE_MODULES))
84
+ }
85
+
86
+ // `root` is the repository the checkout is cut from; the work in progress is read from the process's
87
+ // own repository, which is the same one wherever `josh test:related` calls this.
88
+ async function check_out(root: string, directory: string, head: string): Promise<void> {
89
+ await git(directory, ['init', '--quiet'])
90
+ await add_origin(root, directory)
91
+ await borrow_objects(root, directory)
92
+ await git(directory, ['checkout', '--quiet', '--detach', head])
93
+ writeFileSync(path.join(directory, '.git', 'shallow'), `${head}\n`)
94
+ }
95
+
96
+ async function fill(root: string, directory: string): Promise<void> {
97
+ const head = await git_command.head_commit()
98
+
99
+ await check_out(root, directory, head)
100
+ link_dependencies(root, directory)
101
+
102
+ const work = await changed_paths.read_changed_since(head)
103
+
104
+ for (const file of work) copy_work(root, directory, file)
105
+ }
106
+
107
+ function remove(directory: string): void {
108
+ rmSync(directory, { force: true, recursive: true })
109
+ }
110
+
111
+ // A checkout that could not be finished is removed before the failure is passed on.
112
+ async function create(root: string): Promise<string> {
113
+ const directory = mkdtempSync(path.join(tmpdir(), CHECKOUT_PREFIX))
114
+
115
+ try {
116
+ await fill(root, directory)
117
+ } catch (error) {
118
+ remove(directory)
119
+ throw error
120
+ }
121
+
122
+ return directory
123
+ }
124
+
125
+ const no_base_checkout = { create, remove }
126
+
127
+ export { no_base_checkout }
@@ -88,12 +88,20 @@ async function pop(selector: string, directory?: string): Promise<void> {
88
88
  // — the resume path documented in `backlogrun-park.md` ("expect to resolve conflicts"). The non-zero
89
89
  // exit is indistinguishable from a real failure by its code alone, so a conflicted pop is told apart
90
90
  // by the unmerged paths it leaves: `--diff-filter=U` lists exactly those, empty on any other failure.
91
- async function has_conflict(directory?: string): Promise<boolean> {
91
+ // Read from git rather than parsed from a status string (`main_merge_guard.unmerged_paths`), since a
92
+ // stash caller holds a directory, not a status.
93
+ async function unmerged_paths(directory?: string): Promise<Array<string>> {
92
94
  const unmerged = await git_spawn.read(
93
95
  git_args(directory, ['diff', NAME_ONLY_FLAG, '--diff-filter=U']),
94
96
  )
95
97
 
96
- return unmerged !== ''
98
+ return unmerged.split('\n').filter((line) => line !== '')
99
+ }
100
+
101
+ async function has_conflict(directory?: string): Promise<boolean> {
102
+ const unmerged = await unmerged_paths(directory)
103
+
104
+ return unmerged.length > 0
97
105
  }
98
106
 
99
107
  // Uncommitted work is anything `git status --porcelain` lists — untracked files included, which is what
@@ -211,6 +219,7 @@ const git_stash = {
211
219
  push,
212
220
  select,
213
221
  STASHABLE_PATHSPEC,
222
+ unmerged_paths,
214
223
  work_message,
215
224
  }
216
225
 
@@ -1,3 +1,4 @@
1
+ import { main_merge_guard } from '#scripts/git/main-merge-guard'
1
2
  import { error_text } from '#scripts/lib/error-message'
2
3
  import { git_stash } from './git-stash'
3
4
  import { stash_args, type Request } from './stash-args'
@@ -16,6 +17,7 @@ import { stash_args, type Request } from './stash-args'
16
17
  // can call it unconditionally and pop only after `stashed`.
17
18
 
18
19
  const COMMAND = 'stash:push'
20
+ const STASH_RERUN = 'the command that stashed (`run:pause`, `stash:push`, `latest:if-required`)'
19
21
 
20
22
  const STASHED_VERDICT = 'stashed'
21
23
  const CLEAN_VERDICT = 'clean'
@@ -40,7 +42,15 @@ type PushVerdict = typeof STASHED_VERDICT | typeof CLEAN_VERDICT
40
42
 
41
43
  // The decision and the push without the printing, so a caller that chains a pop on `stashed`
42
44
  // (`josh latest:if-required`) reads the verdict instead of parsing stdout.
45
+ // An index with unresolved paths is refused before the push: `git stash` cannot set them aside, and
46
+ // the way out is closing the merge, not a stash (joshuafolkken/kit#4769).
43
47
  async function set_aside(request: Request): Promise<PushVerdict> {
48
+ const unmerged = await git_stash.unmerged_paths(request.dir)
49
+
50
+ if (unmerged.length > 0) {
51
+ throw new Error(main_merge_guard.unresolved_message(COMMAND, unmerged, STASH_RERUN))
52
+ }
53
+
44
54
  if (!(await git_stash.has_stashable_changes(request.dir))) return CLEAN_VERDICT
45
55
 
46
56
  await git_stash.push(request.message, request.dir, git_stash.STASHABLE_PATHSPEC)
@@ -1,4 +1,5 @@
1
- import { hook_decision } from '#scripts/josh/hook-decision'
1
+ import { will_refuse as will_investigation_refuse } from '#scripts/delegation/investigation-guard'
2
+ import { hook_decision, type GuardRun } from '#scripts/josh/hook-decision'
2
3
  import { lane_guard_policy, type LaneGuardMode } from '#scripts/lane/lane-guard-policy'
3
4
  import { time_batch_guard, type GuardedCall } from '#scripts/time-runtime/time-batch-guard'
4
5
 
@@ -40,29 +41,39 @@ function is_batch_candidate(call: GuardedCall): boolean {
40
41
 
41
42
  // **How the batching guard behaves for the session in hand, decided from the one-place enumeration**.
42
43
  // Outside a lane child the mode is `refuse` and the guard is exactly what it always was; in a dispatched
43
- // lane child it is `notice` — kit#2178 took it to `off` after kit#2164's generic notice did not move the
44
- // density, and kit#2276 restores it as a notice that names the concrete recent calls and recurs every
45
- // single-call turn, the two things #2164's notice lacked, so the density can be re-measured with the
46
- // notice actually firing where it is measured. The wrappers below are three-valued so this needs no
47
- // change beyond the enumeration. The enumeration reads `is_child_of` — the dispatch mark against this
48
- // checkout's own issue — only for a guard whose mode it would actually change, so a person working in a
49
- // lane sees the guard unchanged.
44
+ // lane child the general rule is `off` and only the `search-batching` row still refuses — a chain of
45
+ // searches issued one per turn, the largest share of the child's single-call pairs (kit#5342). The
46
+ // enumeration reads `is_child_of` — the dispatch mark against this checkout's own issue — only for a
47
+ // guard whose mode it would actually change, so a person working in a lane sees the guard unchanged.
50
48
  function batching_mode(): LaneGuardMode {
51
49
  return lane_guard_policy.mode_here('batching')
52
50
  }
53
51
 
54
- // A guard the enumeration turns fully `off` says nothing at all. The lane-child mode and its reason
55
- // live in `lane-guard-policy.ts`; on the main line the mode is `refuse`.
52
+ function is_search_refused_here(): boolean {
53
+ return lane_guard_policy.mode_here('search-batching') === 'refuse'
54
+ }
55
+
56
+ // Where the general rule is `off`, only a search the narrower refusal judges is read for. The
57
+ // lane-child modes and their reasons live in `lane-guard-policy.ts`; on the main line both are `refuse`.
56
58
  function is_candidate(call: GuardedCall): boolean {
57
- return is_batch_candidate(call) && batching_mode() !== 'off'
59
+ if (batching_mode() !== 'off') return is_batch_candidate(call)
60
+
61
+ return is_search_refused_here() && time_batch_guard.is_search_chain_call(call)
58
62
  }
59
63
 
60
- // A refusable call is refused only where the mode is `refuse`. Under any other mode it is withheld the
61
- // refusal; why a lane child takes that mode is its `because` in `lane-guard-policy.ts`.
62
- function should_block_here(tail: string, call: GuardedCall, refused_at_ms: number): boolean {
63
- if (batching_mode() !== 'refuse') return false
64
+ // A refusable call is refused only where the mode is `refuse`; elsewhere a search chain alone is, under
65
+ // the `search-batching` row, standing aside for the investigation guard's refusal of the same call.
66
+ // Why a lane child takes each mode is its `because` in `lane-guard-policy.ts`.
67
+ function should_block_here(
68
+ tail: string,
69
+ call: GuardedCall,
70
+ refused_at_ms: number,
71
+ run: GuardRun,
72
+ ): boolean {
73
+ if (batching_mode() === 'refuse') return time_batch_guard.should_block(tail, call, refused_at_ms)
74
+ if (!is_search_refused_here() || will_investigation_refuse(tail, call, run)) return false
64
75
 
65
- return time_batch_guard.should_block(tail, call, refused_at_ms)
76
+ return time_batch_guard.should_block_search_chain(tail, call, refused_at_ms)
66
77
  }
67
78
 
68
79
  // The whole-file write earns a notice on every run. The second branch turns a
@@ -81,9 +92,8 @@ function should_notify_here(tail: string, call: GuardedCall, notified_at_ms: num
81
92
  }
82
93
 
83
94
  // The wording each notice carries, with the concrete calls the run just issued one-per-turn appended.
84
- // The whole-file write keeps its own base text; the lane notice is reached
85
- // under the `notice`-mode guard batching takes in a lane child again since kit#2276, so a headless
86
- // child is shown *which* recent calls it could have batched rather than only told to batch.
95
+ // The whole-file write keeps its own base text; the lane notice is reached only under a `notice`-mode
96
+ // batching row, so a headless child would be shown *which* recent calls it could have batched.
87
97
  function notice_text(call: GuardedCall, tail: string): string {
88
98
  const base = time_batch_guard.is_notice_call(call)
89
99
  ? time_batch_guard.NOTICE
@@ -99,8 +109,7 @@ const GUARD = hook_decision.create_transcript_guard({
99
109
  should_block: should_block_here,
100
110
  reason: time_batch_guard.REASON,
101
111
  // The whole-file write is notified rather than refused, on a record of its own so it never spends
102
- // the refusal's stamp; the lane-child refusable-call notice kit#2164 added
103
- // here fires again since kit#2276 restored batching to `notice` in a lane child.
112
+ // the refusal's stamp; the refusable-call notice fires only under a `notice`-mode batching row.
104
113
  notify: {
105
114
  prefix: time_batch_guard.NOTICE_STAMP_PREFIX,
106
115
  should_notify: should_notify_here,
@@ -117,6 +126,15 @@ const notice_refusal_path = hook_decision.create_refusal_stamp(
117
126
  time_batch_guard.NOTICE_STAMP_PREFIX,
118
127
  ).path
119
128
  const { deny_envelope, load_environment_file, DISABLED_VALUES } = hook_decision
129
+ const STAMP = hook_decision.create_refusal_stamp(time_batch_guard.STAMP_PREFIX)
130
+
131
+ // Whether this guard refuses the call in hand, asked with its real record — the rule guard stands
132
+ // aside for it, so the two never decide apart.
133
+ function will_refuse(tail: string, call: GuardedCall, run: GuardRun): boolean {
134
+ if (!is_enabled() || !is_candidate(call)) return false
135
+
136
+ return should_block_here(tail, call, STAMP.last_ms(STAMP.path(run.transcript)), run)
137
+ }
120
138
 
121
139
  export {
122
140
  batch_outcome,
@@ -126,6 +144,7 @@ export {
126
144
  load_environment_file,
127
145
  notice_refusal_path,
128
146
  refusal_path,
147
+ will_refuse,
129
148
  DISABLED_VALUES,
130
149
  SWITCH_ENV_KEY,
131
150
  }
@@ -1,5 +1,6 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { resolve_local_bin } from '#scripts/build/local-bin'
3
4
  import { HOOK_PROCESS_TIMEOUT_MS } from '#scripts/lib/timeouts'
4
5
  import { execa } from 'execa'
5
6
 
@@ -38,15 +39,28 @@ const CSPELL_EXTENSIONS: ReadonlySet<string> = new Set([
38
39
  // exit code is ignored: cspell exits non-zero precisely when it found words, which is the case this
39
40
  // exists for.
40
41
  const CSPELL_ARGS: ReadonlyArray<string> = [
41
- 'exec',
42
- 'cspell',
43
42
  'lint',
44
43
  '--words-only',
45
44
  '--unique',
46
45
  '--no-progress',
47
46
  '--no-summary',
48
47
  ]
48
+ const CSPELL_BIN = 'cspell'
49
49
  const PNPM = 'pnpm'
50
+ // The text arrives on stdin under the edited file's own name, so the project config — its ignore
51
+ // list, its language per extension, its overrides — applies exactly as it does to the path. Reading
52
+ // stdin rather than the file is what lets the check run beside the formatters
53
+ // (joshuafolkken/kit#4596): they rewrite the file while it runs, and a read caught mid-write would
54
+ // report on a truncated file.
55
+ const STDIN_SCHEME = 'stdin://'
56
+
57
+ interface CspellRequest {
58
+ file_path: string
59
+ project_root: string
60
+ text: string
61
+ }
62
+
63
+ type CspellRunner = (request: CspellRequest) => Promise<string>
50
64
 
51
65
  const HEADER =
52
66
  'The edit hook found words cspell does not know; fix them or add them to `cspell.config.yaml` now rather than at the gate:'
@@ -54,8 +68,6 @@ const HEADER =
54
68
  const MAX_DIAGNOSTIC_CHARS = 4000
55
69
  const TRUNCATION_NOTICE = '\n…(unknown words truncated)'
56
70
 
57
- type CspellRunner = (file_path: string, project_root: string) => Promise<string>
58
-
59
71
  // One question before a process starts: is this a file under the project that spell-checking has
60
72
  // anything to say about?
61
73
  function is_checkable(file_path: string, project_root: string): boolean {
@@ -68,12 +80,24 @@ function is_checkable(file_path: string, project_root: string): boolean {
68
80
  return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative)
69
81
  }
70
82
 
83
+ // The project's own shim, spawned directly: `pnpm exec` costs a second process start on every edit,
84
+ // and this check was the largest step of the edit hook (joshuafolkken/kit#4596). `pnpm exec` stays as
85
+ // the route for a project whose binaries live somewhere other than the root's `node_modules/.bin`.
86
+ function cspell_command(project_root: string): ReadonlyArray<string> {
87
+ const shim = resolve_local_bin(project_root, CSPELL_BIN)
88
+
89
+ return existsSync(shim) ? [shim] : [PNPM, 'exec', CSPELL_BIN]
90
+ }
91
+
71
92
  // The live runner. `reject: false` because a non-zero exit is the ordinary way cspell reports words,
72
93
  // and stderr is ignored so a config note never reaches the model as if it were an unknown word.
73
- async function run_cspell(file_path: string, project_root: string): Promise<string> {
74
- const result = await execa(PNPM, [...CSPELL_ARGS, file_path], {
94
+ async function run_cspell(request: CspellRequest): Promise<string> {
95
+ const [bin = PNPM, ...prefix] = cspell_command(request.project_root)
96
+ const target = `${STDIN_SCHEME}${path.resolve(request.file_path)}`
97
+ const result = await execa(bin, [...prefix, ...CSPELL_ARGS, target], {
75
98
  reject: false,
76
- cwd: project_root,
99
+ cwd: request.project_root,
100
+ input: request.text,
77
101
  stdout: 'pipe',
78
102
  stderr: 'ignore',
79
103
  timeout: HOOK_PROCESS_TIMEOUT_MS,
@@ -104,22 +128,24 @@ function format_unknown_words(stdout: string): string | undefined {
104
128
 
105
129
  // Never turns a successful edit into a failure: a runner that throws — cspell missing, a timeout — is
106
130
  // swallowed and reported as nothing to say, exactly as the lint half swallows a formatter that could
107
- // not start.
131
+ // not start. `text` is the file's content as the caller read it, so the check does not depend on when
132
+ // it runs against the formatters.
108
133
  async function spelling_diagnostics(
109
134
  file_path: string | undefined,
110
135
  project_root: string,
136
+ text: string,
111
137
  runner: CspellRunner = run_cspell,
112
138
  ): Promise<string | undefined> {
113
139
  if (file_path === undefined || !is_checkable(file_path, project_root)) return undefined
114
140
 
115
141
  try {
116
- return format_unknown_words(await runner(file_path, project_root))
142
+ return format_unknown_words(await runner({ file_path, project_root, text }))
117
143
  } catch {
118
144
  return undefined
119
145
  }
120
146
  }
121
147
 
122
- const edited_cspell = { format_unknown_words, spelling_diagnostics }
148
+ const edited_cspell = { cspell_command, format_unknown_words, run_cspell, spelling_diagnostics }
123
149
 
124
- export type { CspellRunner }
150
+ export type { CspellRequest, CspellRunner }
125
151
  export { edited_cspell, MAX_DIAGNOSTIC_CHARS }