@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
@@ -0,0 +1,194 @@
1
+ import { existsSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { git_spawn } from '#scripts/git/git-spawn'
4
+ import { import_graph, type ImportGraph } from '#scripts/lint/import-graph'
5
+ import { test_declared_logic } from './test-declared-logic'
6
+ import { test_pinned_cache_logic } from './test-pinned-cache-logic'
7
+ import { test_pinned_spawn, type TestSpawn } from './test-pinned-spawn'
8
+ import { related_scope } from './test-related-scope'
9
+ import { whole_suite_files } from './whole-suite-files'
10
+
11
+ // What one `josh test:pinned` verdict rests on, read off the import graph (joshuafolkken/kit#4873): the
12
+ // `basis` `test-pinned-cache-logic.ts` holds a verdict by past the tree-wide digests. The graph is built
13
+ // on the first basis asked for, so a rerun the tree-wide digests already hold builds none — unless a
14
+ // changed fixture asks `probed_files` for its reach.
15
+ //
16
+ // **Where no graph can be built (no `typescript`, no `tsconfig.json`) every basis is undefined**, and a
17
+ // verdict holds only by the tree-wide digests, as before.
18
+
19
+ const NUL = '\0'
20
+ const FIXTURE_MARK = '-fixture'
21
+
22
+ // `related` names the tests reaching a file, which `test-pinned-cache-logic.ts` turns into a basis.
23
+ interface Reach {
24
+ related: (file: string) => Promise<Array<string> | undefined>
25
+ failed: (tests: ReadonlyArray<string>) => Promise<string | undefined>
26
+ }
27
+
28
+ // What a basis is read against: the base commit, each changed non-test path with its content's digest,
29
+ // and each test file's.
30
+ interface ReachInputs {
31
+ commit: string
32
+ code: ReadonlyArray<string>
33
+ digests: ReadonlyMap<string, string>
34
+ test_digests: ReadonlyMap<string, string>
35
+ }
36
+
37
+ // One file and how many test files its imports reach.
38
+ interface ReachedCount {
39
+ file: string
40
+ count: number
41
+ }
42
+
43
+ interface GraphView {
44
+ graph: ImportGraph
45
+ universe: ReadonlyArray<string>
46
+ spawn: TestSpawn
47
+ }
48
+
49
+ // Every file in the work tree, tracked or untracked but not ignored. `-z` lists a path as it is,
50
+ // where `core.quotePath` would quote a non-ASCII one.
51
+ async function work_tree_files(): Promise<Array<string>> {
52
+ const listed = await git_spawn.read(['ls-files', '-coz', '--exclude-standard'])
53
+
54
+ return listed.split(NUL).filter((file) => file !== '')
55
+ }
56
+
57
+ // Every code file the work tree holds, as absolute paths the graph resolves to.
58
+ async function code_universe(): Promise<Array<string>> {
59
+ const files = await work_tree_files()
60
+ const code = files.filter((file) => related_scope.is_relatable(file) && existsSync(file))
61
+
62
+ return code.map((file) => path.resolve(file))
63
+ }
64
+
65
+ async function view_of(): Promise<GraphView | undefined> {
66
+ const graph = await import_graph.create(process.cwd())
67
+ if (graph === undefined) return undefined
68
+
69
+ const universe = await code_universe()
70
+
71
+ return { graph, universe, spawn: test_pinned_spawn.create(graph, universe) }
72
+ }
73
+
74
+ // A code file nothing imports that is not a test: a config, a setup file or a CLI a test spawns.
75
+ function is_root(view: GraphView, file: string): boolean {
76
+ const relative = path.relative(process.cwd(), file)
77
+ if (test_declared_logic.is_test_file(relative)) return false
78
+
79
+ return view.graph.importers([file], view.universe).size <= 1
80
+ }
81
+
82
+ // A changed path no graph ties to the tests that read it: a non-code file a test reads, or one every
83
+ // test depends on without importing it (the vitest config, the setup files, the lock).
84
+ function is_shared(file: string): boolean {
85
+ return (
86
+ !related_scope.is_relatable(file) ||
87
+ whole_suite_files.WHOLE_SUITE_FILES.has(path.normalize(file))
88
+ )
89
+ }
90
+
91
+ // A shared path, or a code file a root reaches — the root itself or a module it imports, which a test
92
+ // spawning it runs past any closure.
93
+ function is_asset(view: GraphView, file: string): boolean {
94
+ if (is_shared(file)) return true
95
+ const reached = view.graph.runtime_importers([path.resolve(file)], view.universe)
96
+
97
+ return [...reached].some((reader) => is_root(view, reader))
98
+ }
99
+
100
+ // The mark of the test-only modules `package.json`'s `files` keeps out of the published package.
101
+ function is_fixture(file: string): boolean {
102
+ return path.basename(file).includes(FIXTURE_MARK)
103
+ }
104
+
105
+ // The changed files `josh test:pinned` probes (joshuafolkken/kit#4879): a fixture no root reaches only
106
+ // moves what its tests cover, so its hunks hold no runtime behavior a test could pin. One a root reaches is
107
+ // kept, and so is every fixture where no graph can be built; the graph is built only for a fixture.
108
+ async function probed_files(files: ReadonlyArray<string>): Promise<Array<string>> {
109
+ if (files.every((file) => !is_fixture(file))) return [...files]
110
+ const view = await view_of()
111
+ if (view === undefined) return [...files]
112
+
113
+ return files.filter((file) => !is_fixture(file) || is_asset(view, file))
114
+ }
115
+
116
+ // The test files whose runtime imports reach `file` — the graph vitest walks (joshuafolkken/kit#5368).
117
+ function related_of(view: GraphView, file: string): Array<string> {
118
+ const reached = view.graph.runtime_importers([path.resolve(file)], view.universe)
119
+
120
+ return [...reached]
121
+ .map((reader) => path.relative(process.cwd(), reader))
122
+ .filter((reader) => test_declared_logic.is_test_file(reader))
123
+ }
124
+
125
+ // How many test files the imports of `files` reach, a listed test counting itself — what
126
+ // `josh test:related` sizes its core claim by before vitest walks the same graph
127
+ // (joshuafolkken/kit#5135). Undefined where no graph can be built.
128
+ async function reached_test_count(files: ReadonlyArray<string>): Promise<number | undefined> {
129
+ const view = await view_of()
130
+ if (view === undefined) return undefined
131
+
132
+ return new Set(files.flatMap((file) => related_of(view, file))).size
133
+ }
134
+
135
+ // Each file's own `reached_test_count`, over one graph — what `josh test:reach` prints
136
+ // (joshuafolkken/kit#5238). Undefined where no graph can be built.
137
+ async function reached_test_counts(
138
+ files: ReadonlyArray<string>,
139
+ ): Promise<Array<ReachedCount> | undefined> {
140
+ const view = await view_of()
141
+ if (view === undefined) return undefined
142
+
143
+ return files.map((file) => ({ file, count: related_of(view, file).length }))
144
+ }
145
+
146
+ // None where no failed test is named or one is gone: what such a red rests on, no graph names. Past
147
+ // the failed tests' closures it rests on the shared paths and on each file they can start, with its
148
+ // closure (joshuafolkken/kit#5261) — a root no failed test names moves no verdict of theirs.
149
+ function failed_of(
150
+ view: GraphView,
151
+ tests: ReadonlyArray<string>,
152
+ inputs: ReachInputs,
153
+ ): string | undefined {
154
+ if (tests.length === 0 || tests.some((test) => !inputs.test_digests.has(test))) return undefined
155
+ const started = view.spawn.started(tests.map((test) => path.resolve(test)))
156
+ const run = [...tests, ...[...started].map((file) => path.relative(process.cwd(), file))]
157
+ const closures = run.map((file) => [file, view.graph.closure_hash(path.resolve(file))] as const)
158
+ const assets = inputs.code.filter((file) => is_shared(file) || started.has(path.resolve(file)))
159
+ const change = test_pinned_cache_logic.change_of(inputs.commit, assets, inputs.digests)
160
+
161
+ return test_pinned_cache_logic.red_basis(change, closures)
162
+ }
163
+
164
+ function create(inputs: ReachInputs): Reach {
165
+ const held: { view?: Promise<GraphView | undefined> } = {}
166
+
167
+ async function related(file: string): Promise<Array<string> | undefined> {
168
+ held.view ??= view_of()
169
+ const read = await held.view
170
+
171
+ return read === undefined ? undefined : related_of(read, file)
172
+ }
173
+
174
+ async function failed(tests: ReadonlyArray<string>): Promise<string | undefined> {
175
+ held.view ??= view_of()
176
+ const read = await held.view
177
+
178
+ return read === undefined ? undefined : failed_of(read, tests, inputs)
179
+ }
180
+
181
+ return { related, failed }
182
+ }
183
+
184
+ const test_pinned_reach = {
185
+ code_universe,
186
+ create,
187
+ probed_files,
188
+ reached_test_count,
189
+ reached_test_counts,
190
+ work_tree_files,
191
+ }
192
+
193
+ export type { Reach, ReachedCount, ReachInputs }
194
+ export { test_pinned_reach }
@@ -0,0 +1,196 @@
1
+ import { gate_log } from '#scripts/gate/gate-log'
2
+ import type { KeptLog } from '#scripts/gate/verification-tail'
3
+ import { PROJECT_ROOT } from '#scripts/init/init-paths'
4
+ import { stamp_file } from '#scripts/josh/stamp-file'
5
+ import { test_pinned_cache_logic } from './test-pinned-cache-logic'
6
+ import type { PinnedOrigin } from './test-pinned-ledger'
7
+ import { test_pinned_logic, type PinnedReport, type ReportParts } from './test-pinned-logic'
8
+
9
+ // The answer `josh test:pinned --record` leaves for `josh run:review --join` (joshuafolkken/kit#4770):
10
+ // the printed report and its unpinned count, keyed to the checkout. `run:review` starts the check in
11
+ // the background beside the gate and the brief, so the join — a later process — reads it here.
12
+ //
13
+ // **Each answer is filed under its launch's token**, so a read answers only for the token it asks
14
+ // with: a check an earlier start left running can still write after the next start cleared the record,
15
+ // and its answer — about a tree the join never asked about — lands in its own file rather than over
16
+ // this launch's. The token comes from the command line, so the file name carries its digest.
17
+
18
+ const RECORD_PREFIX = 'josh-test-pinned-record-'
19
+ const TOKEN_KEY_LENGTH = 16
20
+ const SITE_KEY_LENGTH = 16
21
+ const NOT_RUN_NOTE = '`josh test:pinned` could not run, so nothing was checked: '
22
+ const FIRST_UNPINNED_FLAG = '--first-unpinned'
23
+ const LIST_PREFIX = 'josh-test-pinned-list-'
24
+ const LIST_SUFFIX = '.log'
25
+ const LIST_HEADER = 'josh test:pinned — every unpinned and unchecked place'
26
+
27
+ type UnpinnedHunk = PinnedReport['unpinned'][number]
28
+
29
+ // `unpinned` holds one key per unpinned place (`site_of`); an identical place in the same file takes its
30
+ // occurrence number, so a copy added later is a place no record holds.
31
+ // `text`, `counts` and `log` are the printed report's parts (`test_pinned_logic.report_text`).
32
+ interface PinnedSummary extends ReportParts {
33
+ unpinned: ReadonlyArray<string>
34
+ // What a check still running reached so far; it is shown, never judged or remembered as the answer.
35
+ is_partial?: boolean
36
+ // A `--first-unpinned` check that stopped before some hunks (joshuafolkken/kit#5153): its unpinned
37
+ // places are not every one, so no reason accepts it as the whole answer.
38
+ is_cut?: boolean
39
+ // How many hunks a cut check did not probe, for the note on the probe that goes on with them
40
+ // (joshuafolkken/kit#5327).
41
+ remaining?: number
42
+ }
43
+
44
+ // A place's key leaves out its line, so merging the default branch in, which moves the line, keeps the
45
+ // key (joshuafolkken/kit#4775): the file, the function, and a digest of the lines the change replaced
46
+ // and the lines it put in their place.
47
+ function site_of(hunk: UnpinnedHunk): string {
48
+ const body = JSON.stringify([hunk.new_count, hunk.old_lines, hunk.new_lines ?? []])
49
+ const key = stamp_file.digest(body).slice(0, SITE_KEY_LENGTH)
50
+ const place = hunk.function_name === undefined ? hunk.file : `${hunk.file} ${hunk.function_name}`
51
+
52
+ return `${place} ${key}`
53
+ }
54
+
55
+ // **The full list goes to a log once any place is folded** (joshuafolkken/kit#5188): the printed report
56
+ // counts the unchecked places by reason, so the list is kept where a `tail` cannot cut it.
57
+ function kept_log(report: PinnedReport, root: string): KeptLog | undefined {
58
+ if (!test_pinned_logic.has_unchecked(report)) return undefined
59
+ const output = test_pinned_logic.format(report)
60
+ const target = stamp_file.stamp_path(LIST_PREFIX, root, LIST_SUFFIX)
61
+
62
+ return { path: gate_log.write_gate_log([{ header: LIST_HEADER, output }], target) }
63
+ }
64
+
65
+ function summary_with(report: PinnedReport, log: KeptLog | undefined): PinnedSummary {
66
+ const summary = {
67
+ ...test_pinned_logic.report_parts(report),
68
+ log,
69
+ unpinned: test_pinned_cache_logic.distinct(report.unpinned.map((hunk) => site_of(hunk))),
70
+ }
71
+
72
+ return report.cut === undefined
73
+ ? summary
74
+ : { ...summary, is_cut: true, remaining: report.cut.length }
75
+ }
76
+
77
+ function summary_of(report: PinnedReport, root: string = PROJECT_ROOT): PinnedSummary {
78
+ return summary_with(report, kept_log(report, root))
79
+ }
80
+
81
+ // What a check still running has reached so far (joshuafolkken/kit#4936): `note` says the rest is to
82
+ // come. It keeps no list: the per-checkout list log is the finished check's, which its record names.
83
+ function partial_summary(report: PinnedReport, note: string): PinnedSummary {
84
+ const summary = summary_with(report, undefined)
85
+
86
+ return { ...summary, text: `${note}\n${summary.text}`, is_partial: true }
87
+ }
88
+
89
+ // A check that could not run checked nothing, so it names no unpinned place and the run fails open.
90
+ function not_run_summary(error: unknown): PinnedSummary {
91
+ const message = error instanceof Error ? error.message : String(error)
92
+
93
+ return { text: `${NOT_RUN_NOTE}${message}\n`, unpinned: [] }
94
+ }
95
+
96
+ function record_path(token: string, root: string = PROJECT_ROOT): string {
97
+ const key = stamp_file.digest(token).slice(0, TOKEN_KEY_LENGTH)
98
+
99
+ return stamp_file.stamp_path(`${RECORD_PREFIX}${key}-`, root)
100
+ }
101
+
102
+ function write(token: string, summary: PinnedSummary, root: string = PROJECT_ROOT): void {
103
+ stamp_file.write_stamp(record_path(token, root), { token, ...summary })
104
+ }
105
+
106
+ function is_text_list(value: unknown): value is Array<string> {
107
+ return Array.isArray(value) && value.every((item) => typeof item === 'string')
108
+ }
109
+
110
+ // A kept log's path, absent from the record when its write failed.
111
+ function log_of(value: unknown): KeptLog | undefined {
112
+ if (typeof value !== 'object' || value === null) return undefined
113
+ const kept = value as Record<string, unknown>
114
+
115
+ return { path: typeof kept['path'] === 'string' ? kept['path'] : undefined }
116
+ }
117
+
118
+ // The counts line and the log: absent from a check that could not run.
119
+ function tail_of(record: Record<string, unknown>): Pick<PinnedSummary, 'counts' | 'log'> {
120
+ const { counts, log } = record
121
+
122
+ return typeof counts === 'string' ? { counts, log: log_of(log) } : {}
123
+ }
124
+
125
+ // The count of hunks a cut check did not probe: absent from a record written before it was kept.
126
+ function remaining_of(record: Record<string, unknown>): Pick<PinnedSummary, 'remaining'> {
127
+ const { remaining } = record
128
+
129
+ return typeof remaining === 'number' ? { remaining } : {}
130
+ }
131
+
132
+ function to_summary(record: Record<string, unknown>): PinnedSummary | undefined {
133
+ const { text, unpinned, is_cut } = record
134
+
135
+ if (typeof text !== 'string' || !is_text_list(unpinned)) return undefined
136
+ const summary = { text, unpinned, ...tail_of(record) }
137
+
138
+ return is_cut === true ? { ...summary, is_cut, ...remaining_of(record) } : summary
139
+ }
140
+
141
+ function parsed(raw: string): Record<string, unknown> | undefined {
142
+ try {
143
+ const value: unknown = JSON.parse(raw)
144
+
145
+ return typeof value === 'object' && value !== null
146
+ ? (value as Record<string, unknown>)
147
+ : undefined
148
+ } catch {
149
+ return undefined
150
+ }
151
+ }
152
+
153
+ function read(token: string, root: string = PROJECT_ROOT): PinnedSummary | undefined {
154
+ const raw = stamp_file.read_stamp_text(record_path(token, root))
155
+ const record = raw === undefined ? undefined : parsed(raw)
156
+
157
+ if (record?.['token'] !== token) return undefined
158
+
159
+ return to_summary(record)
160
+ }
161
+
162
+ function clear(token: string, root: string = PROJECT_ROOT): void {
163
+ stamp_file.remove_stamp(record_path(token, root))
164
+ }
165
+
166
+ // A recorded launch's `josh test:pinned` arguments, the token last. **With no `--unpinned-ok` reason it
167
+ // stops at the first unpinned hunk** (joshuafolkken/kit#5153): the launcher stops on it whatever the
168
+ // rest say. Under a reason every hunk is probed, since a reason accepts the list as the whole answer.
169
+ // `origin` names the launcher on the run's ledger row (joshuafolkken/kit#5262). `is_whole` probes every
170
+ // hunk with no reason: the background probe that goes on past a cut check's stop (joshuafolkken/kit#5327).
171
+ function launch_argv(
172
+ token: string,
173
+ reason: string | undefined,
174
+ origin: PinnedOrigin,
175
+ is_whole = false,
176
+ ): Array<string> {
177
+ const cut = reason === undefined && !is_whole ? [FIRST_UNPINNED_FLAG] : []
178
+
179
+ return ['test:pinned', ...cut, '--origin', origin, '--record', token]
180
+ }
181
+
182
+ const test_pinned_record = {
183
+ clear,
184
+ launch_argv,
185
+ is_text_list,
186
+ not_run_summary,
187
+ parsed,
188
+ partial_summary,
189
+ read,
190
+ record_path,
191
+ summary_of,
192
+ write,
193
+ }
194
+
195
+ export type { PinnedSummary }
196
+ export { test_pinned_record }
@@ -0,0 +1,148 @@
1
+ import type { TypeScriptApi } from '#scripts/review/comment-ranges'
2
+ import type { Identifier, Node } from 'typescript'
3
+ import { test_pinned_functions } from './test-pinned-functions'
4
+ import { test_pinned_logic, type Hunk } from './test-pinned-logic'
5
+
6
+ // Which hunks `josh test:pinned` does not probe as a rewire (joshuafolkken/kit#5235): an import moved
7
+ // to another module under a new name, or a call renamed to it. Put back alone, such a hunk reads a name
8
+ // nothing in the file binds, so a test goes red exactly when it runs that line — the hunk has no
9
+ // behavior of its own to pin. A hunk that changes anything besides the names is still probed.
10
+
11
+ interface Names {
12
+ bound: Set<string>
13
+ used: Set<string>
14
+ imported: Set<string>
15
+ }
16
+
17
+ interface Token {
18
+ kind: number
19
+ text: string
20
+ }
21
+
22
+ function is_called(api: TypeScriptApi, parent: Node, node: Identifier): boolean {
23
+ if (!api.isCallExpression(parent) && !api.isNewExpression(parent)) return false
24
+
25
+ return parent.expression === node || parent.arguments?.includes(node) === true
26
+ }
27
+
28
+ // A read of the name's value: the object of a member access, a callee or an argument. Any other
29
+ // position is left out, so a name read elsewhere never makes a hunk a rewire.
30
+ function is_use(api: TypeScriptApi, node: Identifier): boolean {
31
+ const { parent } = node
32
+ if (api.isPropertyAccessExpression(parent)) return parent.expression === node
33
+
34
+ return is_called(api, parent, node)
35
+ }
36
+
37
+ function is_import(api: TypeScriptApi, parent: Node): boolean {
38
+ return (
39
+ api.isImportSpecifier(parent) || api.isImportClause(parent) || api.isNamespaceImport(parent)
40
+ )
41
+ }
42
+
43
+ // Any node's own name counts as bound — a property name too — so a binding is never missed.
44
+ function add_name(api: TypeScriptApi, node: Identifier, names: Names): void {
45
+ const { parent } = node
46
+ if (is_use(api, node)) names.used.add(node.text)
47
+ if (!('name' in parent) || parent.name !== node) return
48
+ names.bound.add(node.text)
49
+ if (is_import(api, parent)) names.imported.add(node.text)
50
+ }
51
+
52
+ function names_in(api: TypeScriptApi, node: Node, names: Names): void {
53
+ if (api.isIdentifier(node)) add_name(api, node, names)
54
+ api.forEachChild(node, (child) => {
55
+ names_in(api, child, names)
56
+ })
57
+ }
58
+
59
+ function names_of(api: TypeScriptApi, file: string, content: string): Names {
60
+ const names = { bound: new Set<string>(), used: new Set<string>(), imported: new Set<string>() }
61
+
62
+ names_in(api, api.createSourceFile(file, content, api.ScriptTarget.ESNext, true), names)
63
+
64
+ return names
65
+ }
66
+
67
+ // The names read and bound nowhere in the file, globals left out.
68
+ function unbound_of(names: Names): Array<string> {
69
+ return [...names.used].filter(
70
+ (name) => !names.bound.has(name) && !Object.hasOwn(globalThis, name),
71
+ )
72
+ }
73
+
74
+ function tokens_of(api: TypeScriptApi, text: string): Array<Token> {
75
+ const target = api.ScriptTarget.ESNext
76
+ const scanner = api.createScanner(target, true, api.LanguageVariant.Standard, text)
77
+ const end = api.SyntaxKind.EndOfFileToken
78
+ const tokens: Array<Token> = []
79
+
80
+ for (let kind = scanner.scan(); kind !== end; kind = scanner.scan()) {
81
+ tokens.push({ kind, text: scanner.getTokenText() })
82
+ }
83
+
84
+ return tokens
85
+ }
86
+
87
+ // A rename the revert undoes: from a name it leaves unbound to one the file imports now.
88
+ interface Swap {
89
+ from: ReadonlySet<string>
90
+ to: ReadonlySet<string>
91
+ }
92
+
93
+ function is_same_token(token: Token, other: Token | undefined, swap: Swap): boolean {
94
+ if (other?.kind !== token.kind) return false
95
+
96
+ return token.text === other.text || (swap.from.has(token.text) && swap.to.has(other.text))
97
+ }
98
+
99
+ // Whether two token runs differ only where `swap` renames the old token to the new one.
100
+ function is_renamed_only(
101
+ olds: ReadonlyArray<Token>,
102
+ news: ReadonlyArray<Token>,
103
+ swap: Swap,
104
+ ): boolean {
105
+ if (olds.length !== news.length) return false
106
+
107
+ return olds.every((token, index) => is_same_token(token, news[index], swap))
108
+ }
109
+
110
+ function is_import_only(api: TypeScriptApi, file: string, text: string): boolean {
111
+ const source = api.createSourceFile(file, text, api.ScriptTarget.ESNext, true)
112
+
113
+ return source.statements.every((statement) => api.isImportDeclaration(statement))
114
+ }
115
+
116
+ function new_text_of(content: string, hunk: Hunk): string {
117
+ const start = hunk.new_start - 1
118
+
119
+ return content
120
+ .split('\n')
121
+ .slice(start, start + hunk.new_count)
122
+ .join('\n')
123
+ }
124
+
125
+ // The hunk's lines are import declarations alone, or the same code with only the names swapped.
126
+ function is_name_swap(api: TypeScriptApi, hunk: Hunk, new_text: string, swap: Swap): boolean {
127
+ const old_text = hunk.old_lines.join('\n')
128
+ const is_imports = is_import_only(api, hunk.file, old_text)
129
+ if (is_imports && is_import_only(api, hunk.file, new_text)) return true
130
+
131
+ return is_renamed_only(tokens_of(api, old_text), tokens_of(api, new_text), swap)
132
+ }
133
+
134
+ function is_rewire(api: TypeScriptApi, content: string, hunk: Hunk): boolean {
135
+ if (!test_pinned_functions.is_script(hunk.file)) return false
136
+ const after = names_of(api, hunk.file, content)
137
+ const before = names_of(api, hunk.file, test_pinned_logic.revert(content, hunk))
138
+ const unbound_now = new Set(unbound_of(after))
139
+ const unbound = unbound_of(before).filter((name) => !unbound_now.has(name))
140
+ if (unbound.length === 0) return false
141
+ const swap = { from: new Set(unbound), to: after.imported }
142
+
143
+ return is_name_swap(api, hunk, new_text_of(content, hunk), swap)
144
+ }
145
+
146
+ const test_pinned_rewire = { is_rewire }
147
+
148
+ export { test_pinned_rewire }
@@ -0,0 +1,81 @@
1
+ import path from 'node:path'
2
+ import type { ImportGraph } from '#scripts/lint/import-graph'
3
+ import { test_declared_logic } from './test-declared-logic'
4
+
5
+ // The code a failed test can run past its import closure (joshuafolkken/kit#5261): a test starts a CLI
6
+ // by naming its file in a string, so a file a string literal of the test or of a module it imports
7
+ // names is one it may start, and so is a file the strings that one runs name in turn — the command a
8
+ // spawned dispatcher starts by its path. Only these, with their closures, are what a `josh
9
+ // test:pinned` red rests on besides the test's own closure; an edit to code nothing named reaches
10
+ // leaves the red held. A comment starts nothing, so its file names are not read.
11
+ //
12
+ // A name is a file's whole base name in one string or template span, so a file named by another with
13
+ // the same base name counts too, erring toward re-probing. A name built across template spans or
14
+ // written without its extension is not read, so a fix to that CLI leaves its red held.
15
+
16
+ const TOKEN = /[\w.-]+/gu
17
+
18
+ interface TestSpawn {
19
+ started: (tests: ReadonlyArray<string>) => ReadonlySet<string>
20
+ }
21
+
22
+ // Each non-test code file of `universe` under its base name.
23
+ function index_of(universe: ReadonlyArray<string>): Map<string, Array<string>> {
24
+ const index = new Map<string, Array<string>>()
25
+
26
+ for (const file of universe) {
27
+ if (test_declared_logic.is_test_file(path.relative(process.cwd(), file))) continue
28
+ const name = path.basename(file)
29
+
30
+ index.set(name, [...(index.get(name) ?? []), file])
31
+ }
32
+
33
+ return index
34
+ }
35
+
36
+ function names_in(text: string, index: ReadonlyMap<string, ReadonlyArray<string>>): Array<string> {
37
+ return (text.match(TOKEN) ?? []).flatMap((token) => index.get(token) ?? [])
38
+ }
39
+
40
+ // `read` answered once per file for the graph's lifetime, however many reds ask it.
41
+ function memoized(read: (file: string) => Array<string>): (file: string) => Array<string> {
42
+ const known = new Map<string, Array<string>>()
43
+
44
+ return function answer(file: string): Array<string> {
45
+ const found = known.get(file) ?? read(file)
46
+
47
+ known.set(file, found)
48
+
49
+ return found
50
+ }
51
+ }
52
+
53
+ function create(graph: ImportGraph, universe: ReadonlyArray<string>): TestSpawn {
54
+ const index = index_of(universe)
55
+ const names_of = memoized((file) => names_in(graph.strings(file).join(' '), index))
56
+ // What `file` runs names: its own strings and those of every module its imports reach.
57
+ const named_from = memoized((file) => [
58
+ ...new Set([file, ...graph.closure(file)].flatMap((read) => names_of(read))),
59
+ ])
60
+
61
+ function started(tests: ReadonlyArray<string>): ReadonlySet<string> {
62
+ const found = new Set<string>()
63
+ const pending = [...tests]
64
+
65
+ for (let current = pending.pop(); current !== undefined; current = pending.pop()) {
66
+ const next = named_from(current).filter((file) => !found.has(file))
67
+
68
+ for (const file of next) found.add(file)
69
+ pending.push(...next)
70
+ }
71
+
72
+ return found
73
+ }
74
+
75
+ return { started }
76
+ }
77
+
78
+ const test_pinned_spawn = { create }
79
+
80
+ export type { TestSpawn }
81
+ export { test_pinned_spawn }
@@ -0,0 +1,70 @@
1
+ import type { RedProbe } from './test-red-logic'
2
+
3
+ // The stages of one `josh test:pinned` probe (joshuafolkken/kit#5292): a hunk whose file reaches far
4
+ // more test files than import it directly runs those importers first, since a revert's red almost
5
+ // always lands there, and the whole reach only when they do not go red. A narrow file runs its reach
6
+ // once, as before.
7
+
8
+ // A reach of this many test files is wide whatever the direct importers number.
9
+ const WIDE_REACH = 100
10
+ // A reach this many times the direct importers is wide.
11
+ const WIDE_RATIO = 3
12
+
13
+ // What a fresh probe runs: `direct` first where the reach is wide, then `reach` unless `direct` went red.
14
+ interface StagePlan {
15
+ direct?: ReadonlyArray<string> | undefined
16
+ reach: ReadonlyArray<string>
17
+ }
18
+
19
+ // How many probes the direct importers decided, and how many escalated to the whole reach.
20
+ interface Staged {
21
+ direct: number
22
+ escalated: number
23
+ }
24
+
25
+ type StageRun = (files: ReadonlyArray<string>) => Promise<RedProbe>
26
+
27
+ function none(): Staged {
28
+ return { direct: 0, escalated: 0 }
29
+ }
30
+
31
+ // No direct importer, or as many as the reach, leaves no smaller first stage to run.
32
+ function is_wide(reach: number, direct: number): boolean {
33
+ if (direct === 0 || direct >= reach) return false
34
+
35
+ return reach >= WIDE_REACH || reach >= WIDE_RATIO * direct
36
+ }
37
+
38
+ // The plan for `file`, read off its `direct` importers and how many test files its imports `reach`.
39
+ function plan_over(file: string, direct: ReadonlyArray<string>, reach: number): StagePlan {
40
+ return is_wide(reach, direct.length) ? { direct, reach: [file] } : { reach: [file] }
41
+ }
42
+
43
+ // The verdict over `plan`, each stage run through `run` and tallied into `staged`: a red from the
44
+ // direct importers decides, anything else is answered by the whole reach.
45
+ async function staged_verdict(plan: StagePlan, run: StageRun, staged: Staged): Promise<RedProbe> {
46
+ if (plan.direct === undefined) return await run(plan.reach)
47
+ const first = await run(plan.direct)
48
+
49
+ if (first.verdict === 'red') {
50
+ staged.direct += 1
51
+
52
+ return first
53
+ }
54
+
55
+ staged.escalated += 1
56
+
57
+ return await run(plan.reach)
58
+ }
59
+
60
+ // The counts line's tail, empty when no probe ran in stages.
61
+ function counts_suffix(staged: Staged | undefined): string {
62
+ if (staged === undefined || staged.direct + staged.escalated === 0) return ''
63
+
64
+ return ` · direct ${String(staged.direct)} · escalated ${String(staged.escalated)}`
65
+ }
66
+
67
+ const test_pinned_stage = { WIDE_RATIO, WIDE_REACH, counts_suffix, none, plan_over, staged_verdict }
68
+
69
+ export type { Staged, StagePlan }
70
+ export { test_pinned_stage }