@joshuafolkken/kit 1.1784.0 → 1.2114.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 +59 -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 +415 -389
  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 +15 -8
  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,29 +1,49 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { parseArgs } from 'node:util'
4
+ import { review_tree } from '#scripts/review/review-tree'
4
5
  import { test_pinned } from './test-pinned'
6
+ import { test_pinned_ledger, type PinnedOrigin } from './test-pinned-ledger'
5
7
  import { test_pinned_logic, type PinnedReport } from './test-pinned-logic'
8
+ import { test_pinned_record, type PinnedSummary } from './test-pinned-record'
6
9
 
7
- // The `josh test:pinned` entry: read `--max`, run, print one line per unpinned or unchecked hunk, and
8
- // exit non-zero when any checked hunk is unpinned. An unchecked hunk alone does not fail the run — it
10
+ // The `josh test:pinned` entry: read `--max`, run, print one line per unpinned hunk and per unchecked
11
+ // reason (each unchecked place in the `full output:` log), and exit non-zero when any checked hunk is unpinned. An unchecked hunk alone does not fail the run — it
9
12
  // was not asked about.
10
13
 
11
14
  const CLEAN_EXIT = 0
12
15
  const FAILED_EXIT = 1
13
16
  const ARGV_OFFSET = 2
14
17
  const USAGE = [
15
- 'Usage: josh test:pinned [--max <n>] [--help]',
16
- ' revert each changed hunk of a non-test code file and run its related tests;',
18
+ 'Usage: josh test:pinned [--max <n>] [--record <token>] [--origin <run-review|pre-detach|manual>] [--first-unpinned] [--help]',
19
+ ' revert each changed code hunk outside tests and fixtures only they reach, and run its related tests;',
17
20
  ' prints `unpinned <file>:<line>` for a hunk no test went red on, and',
18
- ` \`unchecked <file>:<line>\` past the --max cap (default ${String(test_pinned_logic.DEFAULT_MAX_HUNKS)})`,
21
+ ` \`unchecked <n> — <reason>\` for the hunks past the --max cap (default ${String(test_pinned_logic.DEFAULT_MAX_HUNKS)})`,
22
+ ' or whose revert stopped vitest before any test ran, each place listed in the `full output:` log;',
23
+ ' --record also leaves the answer, under <token>, for `josh run:review --join`;',
24
+ ' --origin names what started the run on its lane ledger row (default manual);',
25
+ ' hunks run fewest reached tests first, and --first-unpinned starts no probe once one is unpinned',
19
26
  ].join('\n')
20
27
 
21
28
  const PARSE_ARGS_OPTIONS = {
22
29
  help: { type: 'boolean', short: 'h', default: false },
30
+ 'first-unpinned': { type: 'boolean', default: false },
23
31
  max: { type: 'string' },
32
+ origin: { type: 'string' },
33
+ record: { type: 'string' },
24
34
  } as const
25
35
 
26
36
  const HELP_FLAGS: ReadonlySet<string> = new Set(['--help', '-h'])
37
+ // A tree still moving after this many checks is answered as not checked rather than waited on.
38
+ const MAX_CHECKS = 3
39
+ const MOVING_NOTE = `the tree kept moving across ${String(MAX_CHECKS)} checks`
40
+
41
+ interface PinnedOptions {
42
+ max_hunks: number
43
+ record_token: string | undefined
44
+ is_first_unpinned: boolean
45
+ origin: PinnedOrigin
46
+ }
27
47
 
28
48
  // A `--max` that is not a whole number above zero is a refusal, not the default.
29
49
  function valid_max(raw: string | undefined): number | undefined {
@@ -32,12 +52,22 @@ function valid_max(raw: string | undefined): number | undefined {
32
52
  return Number.isSafeInteger(max) && max > 0 ? max : undefined
33
53
  }
34
54
 
35
- // `undefined` prints the usage: a help request, an unknown flag, or a missing or invalid `--max`.
36
- function max_hunks_of(argv: ReadonlyArray<string>): number | undefined {
55
+ // `undefined` prints the usage: a help request, an unknown flag, a missing or invalid `--max`, or an
56
+ // `--origin` not among the three.
57
+ function options_of(argv: ReadonlyArray<string>): PinnedOptions | undefined {
37
58
  try {
38
59
  const { values } = parseArgs({ args: [...argv], options: PARSE_ARGS_OPTIONS, strict: true })
60
+ const max_hunks = valid_max(values.max)
61
+ const origin = test_pinned_ledger.origin_of(values.origin)
62
+
63
+ if (max_hunks === undefined || origin === undefined || values.help) return undefined
39
64
 
40
- return values.help ? undefined : valid_max(values.max)
65
+ return {
66
+ max_hunks,
67
+ record_token: values.record,
68
+ is_first_unpinned: values['first-unpinned'],
69
+ origin,
70
+ }
41
71
  } catch {
42
72
  return undefined
43
73
  }
@@ -50,23 +80,59 @@ function print_usage(argv: ReadonlyArray<string>): number {
50
80
  return argv.some((argument) => HELP_FLAGS.has(argument)) ? CLEAN_EXIT : FAILED_EXIT
51
81
  }
52
82
 
53
- function output(report: PinnedReport): string {
54
- const unpinned = String(report.unpinned.length)
55
- const unchecked = String(report.unchecked.length)
56
- const lines = [`unpinned ${unpinned} · unchecked ${unchecked}`, test_pinned_logic.format(report)]
83
+ async function tree_digest(): Promise<string> {
84
+ return JSON.stringify(await review_tree.read_changed_tree())
85
+ }
86
+
87
+ // The recorded check runs beside a review that may fix in place, and it reads the tree at more than
88
+ // one moment; a check the tree moved under is run again, so its answer is about one tree.
89
+ async function settled_run(options: PinnedOptions): Promise<PinnedReport> {
90
+ for (let attempt = 0; attempt < MAX_CHECKS; attempt += 1) {
91
+ // eslint-disable-next-line no-await-in-loop -- each check runs only once the last one saw the tree move
92
+ const before = await tree_digest()
93
+ // eslint-disable-next-line no-await-in-loop -- as above
94
+ const report = await test_pinned.run(
95
+ options.max_hunks,
96
+ options.is_first_unpinned,
97
+ options.origin,
98
+ )
99
+ // eslint-disable-next-line no-await-in-loop -- as above
100
+ if ((await tree_digest()) === before) return report
101
+ }
102
+
103
+ throw new Error(MOVING_NOTE)
104
+ }
105
+
106
+ // `--record` leaves an answer however the run ends — a check that could not run included — so the
107
+ // join waiting on it never waits on a check that died.
108
+ async function recorded_run(options: PinnedOptions, token: string): Promise<PinnedSummary> {
109
+ try {
110
+ const summary = test_pinned_record.summary_of(await settled_run(options))
111
+
112
+ test_pinned_record.write(token, summary)
113
+
114
+ return summary
115
+ } catch (error: unknown) {
116
+ test_pinned_record.write(token, test_pinned_record.not_run_summary(error))
57
117
 
58
- return `${lines.filter((line) => line !== '').join('\n')}\n`
118
+ throw error
119
+ }
59
120
  }
60
121
 
61
122
  async function main(argv: ReadonlyArray<string>): Promise<number> {
62
- const max_hunks = max_hunks_of(argv)
63
- if (max_hunks === undefined) return print_usage(argv)
123
+ const options = options_of(argv)
124
+ if (options === undefined) return print_usage(argv)
64
125
 
65
- const report = await test_pinned.run(max_hunks)
126
+ const summary =
127
+ options.record_token === undefined
128
+ ? test_pinned_record.summary_of(
129
+ await test_pinned.run(options.max_hunks, options.is_first_unpinned, options.origin),
130
+ )
131
+ : await recorded_run(options, options.record_token)
66
132
 
67
- process.stdout.write(output(report))
133
+ process.stdout.write(test_pinned_logic.report_text(summary))
68
134
 
69
- return report.unpinned.length === 0 ? CLEAN_EXIT : FAILED_EXIT
135
+ return summary.unpinned.length === 0 ? CLEAN_EXIT : FAILED_EXIT
70
136
  }
71
137
 
72
138
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
@@ -0,0 +1,73 @@
1
+ // How `josh test:pinned` spreads its hunk checks over detached worktrees (joshuafolkken/kit#4706).
2
+ //
3
+ // **Two reverts in flight never share a tree**: each lane holds a tree of its own and checks its hunks
4
+ // one at a time, so a hunk's run sees only its own revert, as the serial run did. **The lanes fit the
5
+ // worker budget the run was admitted on** — at most one lane per worker, each vitest capped to its
6
+ // part of the budget — and never more than `MAX_TREES`, since each lane checks out a whole worktree.
7
+
8
+ const MAX_TREES = 4
9
+ const UNREAD_BUDGET = 1
10
+
11
+ interface LanePlan {
12
+ lanes: number
13
+ workers: number
14
+ }
15
+
16
+ // `budget` is the vitest workers the run may use; an unread one runs a single lane.
17
+ function lane_plan(count: number, budget: number = UNREAD_BUDGET): LanePlan {
18
+ const lanes = Math.max(1, Math.min(count, budget, MAX_TREES))
19
+
20
+ return { lanes, workers: Math.max(1, Math.floor(budget / lanes)) }
21
+ }
22
+
23
+ // The items each lane takes, dealt round-robin so the lanes stay within one item of each other.
24
+ function deal<T>(items: ReadonlyArray<T>, lanes: number): Array<Array<T>> {
25
+ const groups: Array<Array<T>> = Array.from({ length: lanes }, () => [])
26
+
27
+ for (const [index, item] of items.entries()) groups[index % lanes]?.push(item)
28
+
29
+ return groups.filter((group) => group.length > 0)
30
+ }
31
+
32
+ // The lanes' answers, or the first lane's failure once every lane has ended — a failed lane never
33
+ // returns while the others still run their trees beside whatever the caller does next.
34
+ function settled<R>(outcomes: ReadonlyArray<PromiseSettledResult<R>>): Array<R> {
35
+ const failed = outcomes.find((outcome) => outcome.status === 'rejected')
36
+ if (failed !== undefined) throw failed.reason
37
+
38
+ return outcomes.flatMap((outcome) => (outcome.status === 'fulfilled' ? [outcome.value] : []))
39
+ }
40
+
41
+ // Runs every lane at once; a lane answers its own items in the order it was dealt them.
42
+ async function run_lanes<T, R>(
43
+ items: ReadonlyArray<T>,
44
+ lanes: number,
45
+ run_lane: (group: ReadonlyArray<T>) => Promise<ReadonlyArray<R>>,
46
+ ): Promise<Array<ReadonlyArray<R>>> {
47
+ return settled(
48
+ await Promise.allSettled(deal(items, lanes).map(async (group) => await run_lane(group))),
49
+ )
50
+ }
51
+
52
+ // The one-item slice of lane `index % lanes` that holds item `index`.
53
+ function at(index: number, lanes: number): [number, number] {
54
+ const position = Math.floor(index / lanes)
55
+
56
+ return [position, position + 1]
57
+ }
58
+
59
+ // The lanes' results put back in the items' own order, whatever order the lanes finished in: item
60
+ // `index` sat at `floor(index / lanes)` in lane `index % lanes`.
61
+ async function run_pool<T, R>(
62
+ items: ReadonlyArray<T>,
63
+ lanes: number,
64
+ run_lane: (group: ReadonlyArray<T>) => Promise<ReadonlyArray<R>>,
65
+ ): Promise<Array<R>> {
66
+ const results = await run_lanes(items, lanes, run_lane)
67
+
68
+ return items.flatMap((_item, index) => results[index % lanes]?.slice(...at(index, lanes)) ?? [])
69
+ }
70
+
71
+ const test_pinned_pool = { MAX_TREES, deal, lane_plan, run_pool }
72
+
73
+ export { test_pinned_pool }
@@ -0,0 +1,254 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import { base_tree, type BaseTree } from '#scripts/git/base-tree'
4
+ import { direct_importers } from './direct-importers'
5
+ import { related_heavy_tests } from './related-heavy-tests'
6
+ import { test_pinned_cache_logic, type Recheck } from './test-pinned-cache-logic'
7
+ import { test_pinned_ledger, type PinnedOrigin, type ProbeTime } from './test-pinned-ledger'
8
+ import { test_pinned_logic, type Hunk } from './test-pinned-logic'
9
+ import { test_pinned_pool } from './test-pinned-pool'
10
+ import { test_pinned_stage, type Staged, type StagePlan } from './test-pinned-stage'
11
+ import { test_red } from './test-red'
12
+ import type { RedProbe } from './test-red-logic'
13
+ import { related_scope, type RelatedScope } from './test-related-scope'
14
+ import { test_unit_guard } from './test-unit-guard'
15
+ import { unit_worker_share } from './unit-worker-share'
16
+
17
+ // The probes of `josh test:pinned` (joshuafolkken/kit#4516): each hunk reverted alone in a detached
18
+ // worktree at HEAD (`base-tree.ts`), and its related tests run. **Each verdict is handed on as soon as
19
+ // it is reached** (joshuafolkken/kit#4936), so the caller stores it before the next hunk runs.
20
+ //
21
+ // A hunk with a `recheck` runs only those tests — the ones added or edited since its stored `green` or
22
+ // `no-test` verdict, or the ones its stored `red` failed in (`test-pinned-cache-logic.ts`); a `red`
23
+ // recheck they no longer fail falls back to a fresh probe, which runs in stages where the file's reach
24
+ // is wide (`test-pinned-stage.ts`).
25
+
26
+ const TREE_PREFIX = 'josh-test-pinned-'
27
+ const HEAD = 'HEAD'
28
+ const NO_TEST: RedProbe = { verdict: 'no-test', failed: [], ran: 0 }
29
+
30
+ // One hunk to probe, with the index of its verdict among the caller's hunks and how many test files
31
+ // its file reaches, where a graph counted them.
32
+ interface Probe {
33
+ hunk: Hunk
34
+ index: number
35
+ recheck?: Recheck | undefined
36
+ reach?: number | undefined
37
+ }
38
+
39
+ type OnVerdict = (index: number, verdict: RedProbe) => Promise<void>
40
+
41
+ async function direct_of(root: string, file: string): Promise<Array<string>> {
42
+ const importers = await direct_importers.importers_of(root, [file])
43
+
44
+ return importers.get(file) ?? []
45
+ }
46
+
47
+ // What a fresh probe of the hunk runs in the tree at `root` (`test-pinned-stage.ts`). A file every
48
+ // test imports stays the special case of joshuafolkken/kit#5122: its direct importers alone, never
49
+ // escalated, since its reach is the whole suite — and `vitest related` handed it runs the whole suite.
50
+ async function plan_of(root: string, probe: Probe): Promise<StagePlan> {
51
+ const { file } = probe.hunk
52
+
53
+ if (direct_importers.every_test_imports_of([file]).length > 0) {
54
+ return { reach: await direct_of(root, file) }
55
+ }
56
+
57
+ if (probe.reach === undefined) return { reach: [file] }
58
+
59
+ return test_pinned_stage.plan_over(file, await direct_of(root, file), probe.reach)
60
+ }
61
+
62
+ // The tests `files` reach in the tree at `root`, the heavy files reached only indirectly left out
63
+ // (`josh test:related`'s rule). `undefined` when there are none.
64
+ function selection_over(
65
+ root: string,
66
+ files: ReadonlyArray<string>,
67
+ workers: number,
68
+ ): Array<string> | undefined {
69
+ if (files.length === 0) return undefined
70
+
71
+ // vitest is handed the paths relative to the tree it runs in, as the exclusion reads them.
72
+ const scope: RelatedScope = { mode: 'related', files, reason: '' }
73
+ const placed: RelatedScope = { ...scope, files: files.map((test) => path.join(root, test)) }
74
+ const flags = related_heavy_tests.exclude_flags(placed, root)
75
+
76
+ return [
77
+ ...related_scope.vitest_arguments(scope, flags),
78
+ ...unit_worker_share.worker_arguments([], workers),
79
+ ]
80
+ }
81
+
82
+ function recheck_selection(recheck: Recheck, workers: number): Array<string> {
83
+ return ['run', ...recheck.recheck, ...unit_worker_share.worker_arguments([], workers)]
84
+ }
85
+
86
+ // The probe's first run: its recheck, or the first stage of its plan.
87
+ async function selection_of(
88
+ root: string,
89
+ probe: Probe,
90
+ workers: number,
91
+ ): Promise<Array<string> | undefined> {
92
+ if (probe.recheck !== undefined) return recheck_selection(probe.recheck, workers)
93
+ const plan = await plan_of(root, probe)
94
+
95
+ return selection_over(root, plan.direct ?? plan.reach, workers)
96
+ }
97
+
98
+ // The run with this one hunk reverted. The file is put back before the next hunk runs, or that hunk's
99
+ // tests would also see this revert.
100
+ async function reverted_run(
101
+ tree: BaseTree,
102
+ hunk: Hunk,
103
+ selection: ReadonlyArray<string> | undefined,
104
+ ): Promise<RedProbe> {
105
+ if (selection === undefined) return NO_TEST
106
+
107
+ const target = path.join(tree.tree, hunk.file)
108
+ const content = await readFile(target, 'utf8')
109
+
110
+ await writeFile(target, test_pinned_logic.revert(content, hunk))
111
+
112
+ try {
113
+ return await test_red.probe_in(tree, selection)
114
+ } finally {
115
+ await writeFile(target, content)
116
+ }
117
+ }
118
+
119
+ // Set once a probe is unpinned in a `--first-unpinned` run (joshuafolkken/kit#5153): no lane starts
120
+ // another probe, and the ones already running finish and hand their verdicts on.
121
+ interface Halt {
122
+ is_set: boolean
123
+ }
124
+
125
+ // What every lane of one run shares: its workers per tree, the caller's hand-off, the probes' times,
126
+ // how many probes ran in stages, and the halt a `--first-unpinned` run carries.
127
+ interface Lane {
128
+ workers: number
129
+ on_verdict: OnVerdict
130
+ times: Array<ProbeTime>
131
+ staged: Staged
132
+ halt?: Halt | undefined
133
+ }
134
+
135
+ // The hunk's verdict over every stage of its plan.
136
+ async function fresh_verdict(tree: BaseTree, probe: Probe, lane: Lane): Promise<RedProbe> {
137
+ const plan = await plan_of(tree.tree, probe)
138
+
139
+ async function run(files: ReadonlyArray<string>): Promise<RedProbe> {
140
+ return await reverted_run(tree, probe.hunk, selection_over(tree.tree, files, lane.workers))
141
+ }
142
+
143
+ return await test_pinned_stage.staged_verdict(plan, run, lane.staged)
144
+ }
145
+
146
+ // The hunk's verdict: its recheck's answer, or a fresh probe where it answers none.
147
+ async function verdict_of(tree: BaseTree, probe: Probe, lane: Lane): Promise<RedProbe> {
148
+ if (probe.recheck === undefined) return await fresh_verdict(tree, probe, lane)
149
+ const selection = recheck_selection(probe.recheck, lane.workers)
150
+ const run = await reverted_run(tree, probe.hunk, selection)
151
+
152
+ return (
153
+ test_pinned_cache_logic.rechecked(probe.recheck, run) ??
154
+ (await fresh_verdict(tree, probe, lane))
155
+ )
156
+ }
157
+
158
+ function halt_on(lane: Lane, verdict: RedProbe): void {
159
+ if (lane.halt !== undefined && test_pinned_logic.is_unpinned(verdict)) lane.halt.is_set = true
160
+ }
161
+
162
+ async function timed_verdict(tree: BaseTree, probe: Probe, lane: Lane): Promise<RedProbe> {
163
+ const started = Date.now()
164
+ const verdict = await verdict_of(tree, probe, lane)
165
+
166
+ lane.times.push(test_pinned_ledger.time_of(probe.hunk, verdict, Date.now() - started))
167
+
168
+ return verdict
169
+ }
170
+
171
+ // One hunk at a time within a lane: its hunks share the lane's tree (`test-pinned-pool.ts`). A set
172
+ // halt starts no further hunk, so the lane answers fewer than it was dealt.
173
+ async function verdicts_in(
174
+ tree: BaseTree,
175
+ probes: ReadonlyArray<Probe>,
176
+ lane: Lane,
177
+ ): Promise<Array<RedProbe>> {
178
+ const verdicts: Array<RedProbe> = []
179
+
180
+ for (const probe of probes) {
181
+ if (lane.halt?.is_set === true) break
182
+ // eslint-disable-next-line no-await-in-loop -- the hunks share one tree, so each revert waits for the last restore
183
+ const verdict = await timed_verdict(tree, probe, lane)
184
+
185
+ halt_on(lane, verdict)
186
+ // eslint-disable-next-line no-await-in-loop -- stored before the next hunk runs, so a stopped run keeps it
187
+ await lane.on_verdict(probe.index, verdict)
188
+ verdicts.push(verdict)
189
+ }
190
+
191
+ return verdicts
192
+ }
193
+
194
+ async function pooled(
195
+ paths: ReadonlyArray<string>,
196
+ probes: ReadonlyArray<Probe>,
197
+ lane: Omit<Lane, 'workers'>,
198
+ ): Promise<Array<RedProbe>> {
199
+ if (probes.length === 0) return []
200
+ const plan = test_pinned_pool.lane_plan(probes.length, await test_unit_guard.vitest_share([]))
201
+
202
+ return await test_pinned_pool.run_pool(
203
+ probes,
204
+ plan.lanes,
205
+ async (group) =>
206
+ await base_tree.with_tree(TREE_PREFIX, HEAD, async (tree) => {
207
+ await base_tree.copy_in(tree.tree, paths)
208
+
209
+ return await verdicts_in(tree, group, { ...lane, workers: plan.workers })
210
+ }),
211
+ )
212
+ }
213
+
214
+ // `held` counts the hunks the cache answered, `rewired` the ones left out as a rewire
215
+ // (`test-pinned-rewire.ts`); `is_first_unpinned` starts no probe once one is unpinned.
216
+ interface CheckOptions {
217
+ held?: number
218
+ rewired?: number
219
+ is_first_unpinned?: boolean
220
+ origin?: PinnedOrigin | undefined
221
+ }
222
+
223
+ // The verdicts reached, in the probes' order, and how many of the probes ran in stages.
224
+ interface Checked {
225
+ verdicts: Array<RedProbe>
226
+ staged: Staged
227
+ }
228
+
229
+ // The probes, checked over as many trees as the worker budget allows; `paths` are copied into each.
230
+ // The run's ledger row counts the `held` hunks beside the probed ones' times. Under
231
+ // `is_first_unpinned`, the verdicts are the ones started before the first unpinned verdict.
232
+ async function checked_on(
233
+ paths: ReadonlyArray<string>,
234
+ probes: ReadonlyArray<Probe>,
235
+ on_verdict: OnVerdict,
236
+ options: CheckOptions = {},
237
+ ): Promise<Checked> {
238
+ const started = Date.now()
239
+ const times: Array<ProbeTime> = []
240
+ const staged = test_pinned_stage.none()
241
+ const halt = options.is_first_unpinned === true ? { is_set: false } : undefined
242
+ const verdicts = await pooled(paths, probes, { on_verdict, times, staged, halt })
243
+
244
+ const skipped = { held: options.held ?? 0, rewired: options.rewired ?? 0, origin: options.origin }
245
+
246
+ await test_pinned_ledger.record(skipped, times, Date.now() - started)
247
+
248
+ return { verdicts, staged }
249
+ }
250
+
251
+ const test_pinned_probe = { checked_on, selection_of }
252
+
253
+ export type { CheckOptions, Checked, Probe }
254
+ export { test_pinned_probe }
@@ -0,0 +1,118 @@
1
+ import { test_pinned_cache_logic, type GreenHunk } from './test-pinned-cache-logic'
2
+ import { test_pinned_logic, type Hunk } from './test-pinned-logic'
3
+
4
+ // What lets a `red` verdict outlive the removal of an unpinned line (joshuafolkken/kit#5004). Removing
5
+ // the line a `green` probe named changes the code change and the failed test's import closure both, yet
6
+ // that line could be reverted with every test green, so the other hunks' `red` verdicts still hold.
7
+ //
8
+ // **The cache records each `green` hunk and `pruned`: the code change with exactly those hunks
9
+ // reverted.** A
10
+ // later run reverts each of its own hunks that is only what is left of a recorded one — the same base
11
+ // lines, its lines a subsequence of that one's — and a `pruned` that reads the same means the code
12
+ // change since differs only by lines removed from `green` hunks. Any other edit reads differently. A
13
+ // hunk that lost lines has a key of its own, so it is probed afresh. A matching hunk is not tied to its
14
+ // recorded position, so a `green` line moved elsewhere in its file reads as one removed — a stretch the
15
+ // diff's line numbers cannot settle, since every edit above a hunk shifts them.
16
+
17
+ // The code change a `pruned` digest is read off: the base commit, each changed non-test path and its
18
+ // content digest.
19
+ interface CodeChange {
20
+ commit: string
21
+ code: ReadonlyArray<string>
22
+ digests: ReadonlyMap<string, string>
23
+ }
24
+
25
+ // A stub probe on an added file puts back a body it wrote, not the base's lines, so it records nothing.
26
+ function is_line_hunk(hunk: Hunk): boolean {
27
+ return hunk.function_name === undefined
28
+ }
29
+
30
+ // The line hunks whose verdict is `green` — the ones the cache records, and the only ones its `pruned`
31
+ // reverts: a hunk that only resembles one stays, so removing it later reads as an edit.
32
+ function greens_reached(
33
+ hunks: ReadonlyArray<Hunk>,
34
+ verdicts: ReadonlyArray<string | undefined>,
35
+ ): Array<Hunk> {
36
+ return hunks.filter((hunk, index) => verdicts[index] === 'green' && is_line_hunk(hunk))
37
+ }
38
+
39
+ function record_of(hunk: Hunk): GreenHunk {
40
+ return { file: hunk.file, old_lines: hunk.old_lines, new_lines: hunk.new_lines ?? [] }
41
+ }
42
+
43
+ // Every line of `part` sits in `whole`, in the same order.
44
+ function is_subsequence(part: ReadonlyArray<string>, whole: ReadonlyArray<string>): boolean {
45
+ const remaining = [...whole]
46
+
47
+ return part.every((line) => remaining.splice(0, remaining.indexOf(line) + 1).at(-1) === line)
48
+ }
49
+
50
+ function is_same_lines(left: ReadonlyArray<string>, right: ReadonlyArray<string>): boolean {
51
+ return JSON.stringify(left) === JSON.stringify(right)
52
+ }
53
+
54
+ // `hunk` is `green` with none of its lines added or changed, only some — or none — removed.
55
+ function is_remnant(hunk: Hunk, green: GreenHunk): boolean {
56
+ if (!is_line_hunk(hunk) || hunk.file !== green.file) return false
57
+
58
+ return (
59
+ is_same_lines(hunk.old_lines, green.old_lines) &&
60
+ is_subsequence(hunk.new_lines ?? [], green.new_lines)
61
+ )
62
+ }
63
+
64
+ // `content` with each of `hunks` reverted, the lowest last so a revert moves none still to come.
65
+ function reverted(content: string, hunks: ReadonlyArray<Hunk>): string {
66
+ const order = hunks.toSorted((left, right) => right.new_start - left.new_start)
67
+ let text = content
68
+
69
+ for (const hunk of order) {
70
+ text = test_pinned_logic.revert(text, hunk)
71
+ }
72
+
73
+ return text
74
+ }
75
+
76
+ // The hunks a later run reverts: each one that is only what is left of a recorded `green` hunk.
77
+ function remnants_of(hunks: ReadonlyArray<Hunk>, greens: ReadonlyArray<GreenHunk>): Array<Hunk> {
78
+ return hunks.filter((hunk) => greens.some((green) => is_remnant(hunk, green)))
79
+ }
80
+
81
+ // The digest of each `green` hunk's file now, with `reverts` reverted; a file the work tree no longer
82
+ // holds has none.
83
+ function pruned_digests(
84
+ contents: ReadonlyMap<string, string>,
85
+ reverts: ReadonlyArray<Hunk>,
86
+ greens: ReadonlyArray<GreenHunk>,
87
+ ): Map<string, string> {
88
+ const files = [...new Set(greens.map((green) => green.file))]
89
+ const pairs = files.flatMap((file) => {
90
+ const content = contents.get(file)
91
+ if (content === undefined) return []
92
+ const own = reverts.filter((hunk) => hunk.file === file)
93
+
94
+ return [[file, test_pinned_cache_logic.digest_of(reverted(content, own))] as const]
95
+ })
96
+
97
+ return new Map(pairs)
98
+ }
99
+
100
+ // The code change with `reverts` reverted. Each `green` file is listed whether or not it still differs
101
+ // from the base, so one a removal put back reads the same as one a revert did.
102
+ function pruned_of(
103
+ code: CodeChange,
104
+ contents: ReadonlyMap<string, string>,
105
+ reverts: ReadonlyArray<Hunk>,
106
+ greens: ReadonlyArray<GreenHunk>,
107
+ ): string {
108
+ const pruned = pruned_digests(contents, reverts, greens)
109
+ const digests = new Map([...code.digests, ...pruned])
110
+ const paths = [...new Set([...code.code, ...pruned.keys()])]
111
+
112
+ return test_pinned_cache_logic.change_of(code.commit, paths, digests)
113
+ }
114
+
115
+ const test_pinned_prune_logic = { greens_reached, record_of, is_remnant, remnants_of, pruned_of }
116
+
117
+ export type { CodeChange }
118
+ export { test_pinned_prune_logic }