@joshuafolkken/kit 1.1784.0 → 1.2116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (690) hide show
  1. package/.claude/agents/reviewer-verification.md +19 -0
  2. package/.claude/agents/reviewer.md +19 -0
  3. package/.claude/settings.json +5 -6
  4. package/.claude/skills/dependency-update/SKILL.md +9 -10
  5. package/.claude/skills/workflow-commands/SKILL.md +6 -6
  6. package/.claude/skills/workflow-commands/background-commands.md +8 -5
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  13. package/.claude/skills/workflow-commands/backlogrun.md +1 -1
  14. package/.claude/skills/workflow-commands/chain-rule.md +41 -32
  15. package/.claude/skills/workflow-commands/delegation.md +6 -6
  16. package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
  17. package/.claude/skills/workflow-commands/followup.md +7 -5
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
  19. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  20. package/.claude/skills/workflow-commands/issue-comments.md +9 -4
  21. package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +2 -2
  23. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  24. package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
  25. package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
  26. package/.claude/skills/workflow-commands/prerequisite.md +3 -2
  27. package/.claude/skills/workflow-commands/retrospective.md +2 -2
  28. package/.claude/skills/workflow-commands/split-assessment.md +7 -5
  29. package/.codex/hooks.json +5 -5
  30. package/.github/actions/setup-pnpm/action.yml +9 -16
  31. package/.github/dependabot.yml +16 -0
  32. package/.github/workflows/auto-tag.yml +45 -0
  33. package/.github/workflows/ci.yml +155 -15
  34. package/.github/workflows/dependabot-auto-merge.yml +63 -4
  35. package/.github/workflows/production.yml +8 -0
  36. package/.github/workflows/publish.yml +60 -45
  37. package/.vscode/settings.json +19 -0
  38. package/CLAUDE.md +5 -12
  39. package/SECURITY.md +2 -2
  40. package/dist/CLAUDE.md +5 -12
  41. package/dist/commands/document-read-cli.js +2 -1
  42. package/dist/commands/document-section-main.js +2 -1
  43. package/dist/commands/read-set-cli.js +194 -134
  44. package/dist/commands/run-prep-main.js +9694 -10173
  45. package/dist/commands/run-step-cli.js +22138 -18285
  46. package/dist/hooks/audit-provision.js +23 -5
  47. package/dist/hooks/chunk-3GKE653K.js +6204 -0
  48. package/dist/hooks/chunk-45CJJOO7.js +1243 -0
  49. package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
  50. package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
  51. package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
  52. package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
  53. package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
  54. package/dist/hooks/chunk-VVNYJWEI.js +30 -0
  55. package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
  56. package/dist/hooks/codex-hook-adapter.js +8 -8
  57. package/dist/hooks/format-edited.js +4 -5
  58. package/dist/hooks/inputs.json +386 -360
  59. package/dist/hooks/pretool-guard.js +7 -6
  60. package/dist/hooks/session-lang.js +101 -258
  61. package/dist/hooks/stop-guard.js +1744 -2576
  62. package/dist/josh.js +7133 -6368
  63. package/dist/version/index.js +161 -1
  64. package/eslint/rules/comment-why-only.js +108 -0
  65. package/package.json +16 -9
  66. package/prompts/coding-standards.md +1 -1
  67. package/prompts/collaboration-workflow/file-edits.md +14 -11
  68. package/prompts/collaboration-workflow/issue-citation.md +0 -4
  69. package/prompts/collaboration-workflow/issue-template.md +40 -21
  70. package/prompts/collaboration-workflow/lane-instructions.md +3 -1
  71. package/prompts/collaboration-workflow/measurement.md +8 -1
  72. package/prompts/collaboration-workflow/operating-rules.md +11 -2
  73. package/prompts/collaboration-workflow/output-bounds.md +6 -2
  74. package/prompts/collaboration-workflow/plan-comment.md +1 -1
  75. package/prompts/collaboration-workflow/principles.md +9 -0
  76. package/prompts/collaboration-workflow/residency.md +3 -2
  77. package/prompts/collaboration-workflow/rule-delivery.md +13 -1
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  79. package/prompts/collaboration-workflow/wip-cap.md +3 -1
  80. package/prompts/review-rubric.md +8 -0
  81. package/prompts/review.md +31 -22
  82. package/prompts/testing-guide.md +1 -0
  83. package/scripts/agent/agent-argv.ts +14 -16
  84. package/scripts/agent/agent-provider.ts +138 -0
  85. package/scripts/agent/agent-role-profile.ts +108 -156
  86. package/scripts/agent/agent-safe-text.ts +20 -5
  87. package/scripts/agent/api-usage-limit.ts +65 -0
  88. package/scripts/agent/claude-agent-argv.ts +19 -9
  89. package/scripts/agent/codex-agent-argv.ts +118 -22
  90. package/scripts/agent/usage-limit-reset.ts +179 -0
  91. package/scripts/auth/claude-token-output.ts +2 -2
  92. package/scripts/auto-ok/auto-ok-author.ts +53 -0
  93. package/scripts/auto-ok/auto-ok-cli.ts +20 -9
  94. package/scripts/backlog/backlog-defect-priority.ts +1 -1
  95. package/scripts/backlog/backlog-drive-cli.ts +6 -1
  96. package/scripts/backlog/backlog-drive-hold.ts +3 -3
  97. package/scripts/backlog/backlog-drive-idle.ts +10 -0
  98. package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
  99. package/scripts/backlog/backlog-drive-launch.ts +8 -2
  100. package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
  101. package/scripts/backlog/backlog-drive-named.ts +28 -7
  102. package/scripts/backlog/backlog-drive-paused.ts +5 -4
  103. package/scripts/backlog/backlog-drive-restore.ts +11 -3
  104. package/scripts/backlog/backlog-drive.ts +21 -60
  105. package/scripts/backlog/backlog-gate-scope.ts +61 -0
  106. package/scripts/backlog/backlog-measure-close.ts +112 -0
  107. package/scripts/backlog/backlog-next.ts +39 -49
  108. package/scripts/backlog/backlog-occupied.ts +2 -2
  109. package/scripts/backlog/backlog-overlap.ts +42 -8
  110. package/scripts/backlog/backlog-plan-cli.ts +4 -1
  111. package/scripts/backlog/backlog-plan-comment.ts +81 -0
  112. package/scripts/backlog/backlog-plan-read.ts +19 -6
  113. package/scripts/backlog/backlog-pool.ts +50 -12
  114. package/scripts/backlog/backlog-rank.ts +60 -13
  115. package/scripts/backlog/backlog-stalled-detect.ts +2 -1
  116. package/scripts/backlog/backlog-waves.ts +17 -2
  117. package/scripts/backlog/backlogrun-launch.ts +7 -4
  118. package/scripts/cost-runtime/context-cut-payback.ts +27 -25
  119. package/scripts/cost-runtime/cost-cli.ts +37 -16
  120. package/scripts/cost-runtime/cost-pricing.ts +9 -0
  121. package/scripts/cost-runtime/cost-verdict.ts +4 -1
  122. package/scripts/delegation/delegation-policy.ts +2 -2
  123. package/scripts/delegation/duplicate-read-guard.ts +1 -0
  124. package/scripts/delegation/investigation-guard.ts +14 -2
  125. package/scripts/delegation/investigation-reads.ts +45 -23
  126. package/scripts/dependabot/dependabot-cooldown.ts +3 -2
  127. package/scripts/discovery/repo-party.ts +80 -20
  128. package/scripts/discovery/session-owner-pin.ts +51 -0
  129. package/scripts/document/capped-print.ts +1 -1
  130. package/scripts/document/command-reference-line-budget.ts +42 -0
  131. package/scripts/document/document-byte-budget.ts +17 -7
  132. package/scripts/document/document-checks.ts +20 -0
  133. package/scripts/document/document-ratchet-check.ts +101 -0
  134. package/scripts/document/document-section.ts +1 -0
  135. package/scripts/document/entry-read-budget.ts +7 -3
  136. package/scripts/document/issue-citation-budget.ts +94 -0
  137. package/scripts/epic/epic-cli-argv.ts +2 -2
  138. package/scripts/epic/epic-fetch.ts +4 -2
  139. package/scripts/epic/epic-graph.ts +7 -2
  140. package/scripts/epic/epic-issue.ts +3 -1
  141. package/scripts/epic/epic-rank.ts +5 -0
  142. package/scripts/exports/unused-members-check.ts +62 -0
  143. package/scripts/exports/unused-members-cli.ts +6 -51
  144. package/scripts/followup/followup-ci-path.ts +67 -0
  145. package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
  146. package/scripts/followup/followup-coderabbit-wait.ts +13 -10
  147. package/scripts/followup/followup-failed-tests.ts +160 -0
  148. package/scripts/followup/followup-job-needs.ts +100 -0
  149. package/scripts/followup/followup-job-rerun.ts +303 -0
  150. package/scripts/followup/followup-main-move.ts +113 -0
  151. package/scripts/followup/git-followup-finish.ts +43 -11
  152. package/scripts/followup/git-followup-pending.ts +3 -3
  153. package/scripts/followup/git-followup-stages.ts +41 -11
  154. package/scripts/followup/git-pr-followup-checks.ts +55 -16
  155. package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
  156. package/scripts/followup/git-pr-followup.ts +62 -27
  157. package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
  158. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
  159. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
  160. package/scripts/gate/busy-average.ts +34 -2
  161. package/scripts/gate/check-related-pinned.ts +43 -0
  162. package/scripts/gate/check-related-reuse.ts +64 -0
  163. package/scripts/gate/check-related-section.ts +57 -0
  164. package/scripts/gate/check-related-unplanned.ts +34 -0
  165. package/scripts/gate/check-related.ts +265 -0
  166. package/scripts/gate/command-memory.ts +1 -1
  167. package/scripts/gate/core-admission.ts +24 -6
  168. package/scripts/gate/core-budget-timing.ts +20 -6
  169. package/scripts/gate/core-budget.ts +40 -11
  170. package/scripts/gate/core-wait-record.ts +3 -2
  171. package/scripts/gate/core-wait.ts +25 -2
  172. package/scripts/gate/core-weights.ts +15 -7
  173. package/scripts/gate/gate-labels.ts +46 -0
  174. package/scripts/gate/gate-ledger.ts +14 -6
  175. package/scripts/gate/gate-plan.ts +103 -58
  176. package/scripts/gate/gate-report.ts +9 -7
  177. package/scripts/gate/gate-run-steps.ts +26 -3
  178. package/scripts/gate/gate-skip.ts +45 -6
  179. package/scripts/gate/gate-static-group.ts +60 -0
  180. package/scripts/gate/gate-steps.ts +2 -1
  181. package/scripts/gate/gate-tree.ts +4 -2
  182. package/scripts/gate/hook-gate-reuse.ts +19 -2
  183. package/scripts/gate/metrics-totals.ts +59 -0
  184. package/scripts/gate/scoped-green.ts +68 -15
  185. package/scripts/gate/since-green.ts +14 -6
  186. package/scripts/gate/structural-checks.ts +96 -0
  187. package/scripts/gate/verification-gate.ts +51 -16
  188. package/scripts/gate/verification-inputs.ts +21 -6
  189. package/scripts/gate/verification-tail.ts +52 -0
  190. package/scripts/gh/gh-author-association.ts +23 -2
  191. package/scripts/gh/git-ai-review-scan.ts +1 -1
  192. package/scripts/gh/git-gh-api-path.ts +12 -3
  193. package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
  194. package/scripts/gh/git-gh-exec.ts +32 -2
  195. package/scripts/gh/git-gh-issue-read.ts +4 -2
  196. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  197. package/scripts/gh/git-gh-issue-write.ts +8 -2
  198. package/scripts/gh/git-gh-issue.ts +12 -5
  199. package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
  200. package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
  201. package/scripts/gh/git-gh-pr-read.ts +74 -13
  202. package/scripts/gh/git-gh-pr-rest.ts +3 -1
  203. package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
  204. package/scripts/gh/git-gh-pr-too-large.ts +20 -0
  205. package/scripts/gh/git-gh-pr.ts +21 -7
  206. package/scripts/gh/git-gh-retry.ts +141 -0
  207. package/scripts/gh/git-pr-ai-review.ts +15 -6
  208. package/scripts/gh/git-pr-checks-eval.ts +3 -0
  209. package/scripts/gh/git-pr-checks.ts +5 -5
  210. package/scripts/git/base-tree.ts +30 -0
  211. package/scripts/git/change-base.ts +4 -10
  212. package/scripts/git/changed-paths-sync.ts +63 -0
  213. package/scripts/git/changed-paths.ts +16 -1
  214. package/scripts/git/git-common-directory.ts +11 -1
  215. package/scripts/git/git-diff-reads.ts +66 -20
  216. package/scripts/git/git-error.ts +9 -2
  217. package/scripts/git/git-schemas.ts +13 -0
  218. package/scripts/git/git-worktree.ts +13 -0
  219. package/scripts/git/gone-branch.ts +4 -1
  220. package/scripts/git/main-merge-budget-conflict.ts +89 -0
  221. package/scripts/git/main-merge-guard.ts +55 -18
  222. package/scripts/git/main-merge-regenerate.ts +59 -25
  223. package/scripts/git/main-merge.ts +53 -5
  224. package/scripts/git/no-base-checkout.ts +127 -0
  225. package/scripts/git/stash/git-stash.ts +11 -2
  226. package/scripts/git/stash/stash-push-cli.ts +10 -0
  227. package/scripts/hooks/batch-guard.ts +40 -21
  228. package/scripts/hooks/edited-cspell.ts +37 -11
  229. package/scripts/hooks/format-edited-file.ts +22 -2
  230. package/scripts/hooks/lane-plan-guard.ts +50 -0
  231. package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
  232. package/scripts/hooks/pre-commit-lint.ts +14 -1
  233. package/scripts/hooks/pre-push-related-switch.ts +10 -0
  234. package/scripts/hooks/pre-push-unit.ts +76 -10
  235. package/scripts/hooks/pretool-guard.ts +2 -0
  236. package/scripts/hooks/reserved-run.ts +14 -1
  237. package/scripts/hooks/run-hook.sh +23 -8
  238. package/scripts/hooks/step-zero-notice.ts +6 -1
  239. package/scripts/hooks/stop-actions.ts +3 -15
  240. package/scripts/hooks/stop-guard.ts +7 -6
  241. package/scripts/init/distributed-paths.ts +1 -0
  242. package/scripts/init/hook-command-rewrite.ts +5 -1
  243. package/scripts/init/hook-launch.ts +4 -2
  244. package/scripts/issue/aggregation-split.ts +52 -0
  245. package/scripts/issue/defect-origin-line.ts +19 -1
  246. package/scripts/issue/defect-rate-cli.ts +5 -102
  247. package/scripts/issue/defect-report.ts +4 -4
  248. package/scripts/issue/effect-audit-cli.ts +178 -0
  249. package/scripts/issue/effect-audit.ts +185 -0
  250. package/scripts/issue/expected-effect-lint.ts +64 -9
  251. package/scripts/issue/git-next-issues.ts +5 -0
  252. package/scripts/issue/issue-body-or-comment.ts +38 -4
  253. package/scripts/issue/issue-cite-cli.ts +1 -3
  254. package/scripts/issue/issue-classification.ts +1 -0
  255. package/scripts/issue/issue-closing-pr.ts +8 -1
  256. package/scripts/issue/issue-evidence-exit.ts +4 -2
  257. package/scripts/issue/issue-file-cap.ts +58 -0
  258. package/scripts/issue/issue-file-cli.ts +111 -38
  259. package/scripts/issue/issue-file-deferral.ts +52 -0
  260. package/scripts/issue/issue-file-fold.ts +29 -10
  261. package/scripts/issue/issue-file-interrupt.ts +85 -0
  262. package/scripts/issue/issue-file-lane-hint.ts +30 -0
  263. package/scripts/issue/issue-file.ts +43 -6
  264. package/scripts/issue/issue-labels.ts +19 -2
  265. package/scripts/issue/issue-lint-cli.ts +27 -2
  266. package/scripts/issue/issue-measured-cli.ts +13 -35
  267. package/scripts/issue/issue-measured-close.ts +58 -0
  268. package/scripts/issue/issue-not-before-label.ts +2 -2
  269. package/scripts/issue/issue-not-before.ts +4 -1
  270. package/scripts/issue/issue-priority-cli.ts +60 -0
  271. package/scripts/issue/issue-priority-raise.ts +55 -0
  272. package/scripts/issue/issue-priority.ts +174 -43
  273. package/scripts/issue/issue-rank.ts +21 -4
  274. package/scripts/issue/issue-read-cli.ts +32 -6
  275. package/scripts/issue/issue-read.ts +37 -12
  276. package/scripts/issue/issue-sample-gate.ts +95 -74
  277. package/scripts/issue/issue-sample-prime.ts +127 -0
  278. package/scripts/issue/issue-sample-required.ts +57 -0
  279. package/scripts/issue/issue-sample-source.ts +12 -11
  280. package/scripts/issue/issue-search.ts +124 -0
  281. package/scripts/issue/issue-state.ts +13 -1
  282. package/scripts/issue/issue-template-section.ts +43 -0
  283. package/scripts/issue/issue-watch-diff.ts +31 -1
  284. package/scripts/issue/issue-watch-hook.ts +11 -2
  285. package/scripts/issue/issue-watch.ts +55 -12
  286. package/scripts/issue/markdown-section.ts +1 -0
  287. package/scripts/issue/measure-declaration.ts +121 -0
  288. package/scripts/issue/measure-rerun-cli.ts +7 -70
  289. package/scripts/issue/plan-consumers-section.ts +22 -1
  290. package/scripts/issue/reproduction-measure.ts +3 -1
  291. package/scripts/issue/size-estimate.ts +90 -0
  292. package/scripts/issue/trusted-measure.ts +84 -0
  293. package/scripts/josh/command-usage.ts +3 -2
  294. package/scripts/josh/durable-state-migrate.ts +105 -0
  295. package/scripts/josh/durable-state.ts +112 -0
  296. package/scripts/josh/josh-aliases.ts +95 -0
  297. package/scripts/josh/josh-command-map.ts +1 -82
  298. package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
  299. package/scripts/josh/josh-commands-ai.ts +5 -5
  300. package/scripts/josh/josh-commands-development.ts +59 -58
  301. package/scripts/josh/josh-commands-issue-write.ts +23 -4
  302. package/scripts/josh/josh-commands-issue.ts +32 -0
  303. package/scripts/josh/josh-commands-lane.ts +87 -4
  304. package/scripts/josh/josh-commands-lint.ts +16 -3
  305. package/scripts/josh/josh-commands-maintenance.ts +15 -0
  306. package/scripts/josh/josh-commands-review.ts +41 -6
  307. package/scripts/josh/josh-commands-ship.ts +56 -8
  308. package/scripts/josh/josh-commands-test-check.ts +80 -0
  309. package/scripts/josh/josh-commands-workflow.ts +1 -1
  310. package/scripts/josh/josh-environment-file.ts +43 -3
  311. package/scripts/josh/josh-in-process.ts +2 -2
  312. package/scripts/josh/josh-logic.ts +5 -3
  313. package/scripts/josh/josh-run.ts +19 -1
  314. package/scripts/josh/run-record-path.ts +15 -0
  315. package/scripts/josh/stamp-file.ts +11 -2
  316. package/scripts/lane/lane-adopt-cli.ts +98 -0
  317. package/scripts/lane/lane-adopt.ts +208 -0
  318. package/scripts/lane/lane-await.ts +5 -8
  319. package/scripts/lane/lane-baseline.ts +123 -0
  320. package/scripts/lane/lane-breakdown-cli.ts +64 -0
  321. package/scripts/lane/lane-breakdown-implement.ts +170 -0
  322. package/scripts/lane/lane-breakdown-laps.ts +107 -0
  323. package/scripts/lane/lane-breakdown-pinned.ts +76 -0
  324. package/scripts/lane/lane-breakdown-text.ts +145 -0
  325. package/scripts/lane/lane-breakdown.ts +239 -0
  326. package/scripts/lane/lane-check-stats.ts +29 -8
  327. package/scripts/lane/lane-child-invocation.ts +9 -2
  328. package/scripts/lane/lane-child-mark-keys.ts +12 -0
  329. package/scripts/lane/lane-child-marker.ts +20 -1
  330. package/scripts/lane/lane-ci-path-stats.ts +73 -0
  331. package/scripts/lane/lane-claim-stats.ts +84 -0
  332. package/scripts/lane/lane-close.ts +8 -3
  333. package/scripts/lane/lane-dispatch-log.ts +3 -3
  334. package/scripts/lane/lane-dispatch.ts +2 -2
  335. package/scripts/lane/lane-gap-stats.ts +7 -6
  336. package/scripts/lane/lane-guard-policy.ts +7 -1
  337. package/scripts/lane/lane-implement-stats.ts +207 -0
  338. package/scripts/lane/lane-implement-subagents.ts +90 -0
  339. package/scripts/lane/lane-investigation-switch.ts +16 -0
  340. package/scripts/lane/lane-ledger-kind.ts +31 -0
  341. package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
  342. package/scripts/lane/lane-ledger-schema.ts +119 -36
  343. package/scripts/lane/lane-ledger-write.ts +257 -0
  344. package/scripts/lane/lane-ledger.ts +31 -177
  345. package/scripts/lane/lane-load.ts +1 -1
  346. package/scripts/lane/lane-measure-cli.ts +176 -59
  347. package/scripts/lane/lane-net-read.ts +44 -0
  348. package/scripts/lane/lane-net-time.ts +212 -0
  349. package/scripts/lane/lane-open.ts +16 -7
  350. package/scripts/lane/lane-output.ts +2 -4
  351. package/scripts/lane/lane-overrun-prompt.ts +147 -0
  352. package/scripts/lane/lane-overrun.ts +274 -0
  353. package/scripts/lane/lane-paths.ts +24 -1
  354. package/scripts/lane/lane-pause-cli.ts +4 -1
  355. package/scripts/lane/lane-paused-spans.ts +97 -0
  356. package/scripts/lane/lane-plan-check.ts +207 -0
  357. package/scripts/lane/lane-plan-cli.ts +41 -0
  358. package/scripts/lane/lane-plan.ts +144 -0
  359. package/scripts/lane/lane-registry.ts +14 -1
  360. package/scripts/lane/lane-relaunch.ts +1 -1
  361. package/scripts/lane/lane-round-one-stats.ts +51 -0
  362. package/scripts/lane/lane-sampler.ts +23 -4
  363. package/scripts/lane/lane-session-action.ts +93 -18
  364. package/scripts/lane/lane-session-broker.ts +49 -21
  365. package/scripts/lane/lane-session-reread-main.ts +14 -0
  366. package/scripts/lane/lane-session-reread.ts +51 -0
  367. package/scripts/lane/lane-ship-lap-stats.ts +74 -0
  368. package/scripts/lane/lane-sleep.ts +58 -0
  369. package/scripts/lane/lane-spent.ts +1 -1
  370. package/scripts/lane/lane-split-stats.ts +28 -0
  371. package/scripts/lane/lane-stage-stats.ts +13 -1
  372. package/scripts/lane/lane-state.ts +41 -9
  373. package/scripts/lane/lane-stats-tables.ts +58 -0
  374. package/scripts/lane/lane-stats.ts +21 -5
  375. package/scripts/lane/lane-stop-stats.ts +51 -19
  376. package/scripts/lane/lane-tail-stats.ts +32 -0
  377. package/scripts/lane/lane-transcript-calls.ts +67 -10
  378. package/scripts/lane/lane-transcript-stats.ts +44 -12
  379. package/scripts/lane/lane-value-cell.ts +72 -0
  380. package/scripts/lane/lane-value.ts +118 -0
  381. package/scripts/lane/lane-withdraw-closed.ts +124 -0
  382. package/scripts/lane/lane-withdraw.ts +108 -31
  383. package/scripts/lane/lane-yield.ts +85 -0
  384. package/scripts/lane/openai-lane-ship.ts +12 -11
  385. package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
  386. package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
  387. package/scripts/lane/openai-lane-supervisor.ts +5 -4
  388. package/scripts/lib/poll.ts +38 -9
  389. package/scripts/lib/settled-together.ts +10 -0
  390. package/scripts/lib/shell-quote.ts +21 -0
  391. package/scripts/lib/token-shape.ts +111 -0
  392. package/scripts/lint/eslint-daemon-sources.ts +79 -0
  393. package/scripts/lint/eslint-daemon.ts +45 -6
  394. package/scripts/lint/eslint-warm-cli.ts +7 -0
  395. package/scripts/lint/eslint-warm.ts +66 -0
  396. package/scripts/lint/import-graph.ts +141 -11
  397. package/scripts/lint/lint-gate-scope.ts +2 -2
  398. package/scripts/lint/lint-parallel.ts +54 -6
  399. package/scripts/lint/lint-related.ts +154 -33
  400. package/scripts/lint/rename-cli.ts +179 -0
  401. package/scripts/lint/rename-identifier.ts +192 -0
  402. package/scripts/lint/type-only-imports.ts +58 -0
  403. package/scripts/notify/telegram-notify.ts +12 -3
  404. package/scripts/observations/observation-record.ts +42 -16
  405. package/scripts/observations/observations-flush.ts +32 -9
  406. package/scripts/overrides/overrides-check.ts +9 -1
  407. package/scripts/release/release-approval-prune.ts +7 -1
  408. package/scripts/repo/required-checks-logic.ts +4 -1
  409. package/scripts/repo/ruleset-check.ts +4 -3
  410. package/scripts/report/markdown-shape.ts +28 -0
  411. package/scripts/report/report-format-reference.ts +15 -1
  412. package/scripts/report/report-labels.ts +12 -1
  413. package/scripts/report/report-lint-child-summary.ts +94 -0
  414. package/scripts/report/report-lint-cli.ts +26 -7
  415. package/scripts/report/report-lint.ts +2 -20
  416. package/scripts/review/prose-stale-cli.ts +88 -0
  417. package/scripts/review/prose-stale-scan.ts +152 -0
  418. package/scripts/review/review-attest.ts +73 -23
  419. package/scripts/review/review-brief-cli.ts +68 -19
  420. package/scripts/review/review-brief-paths.ts +22 -0
  421. package/scripts/review/review-brief.ts +14 -7
  422. package/scripts/review/review-comment-carry.ts +47 -0
  423. package/scripts/review/review-comment-round.ts +85 -22
  424. package/scripts/review/review-finding-ledger.ts +109 -7
  425. package/scripts/review/review-finding-text.ts +43 -0
  426. package/scripts/review/review-interaction.ts +11 -0
  427. package/scripts/review/review-lane-handover.ts +68 -0
  428. package/scripts/review/review-lint-cli.ts +57 -0
  429. package/scripts/review/review-lint-findings.ts +92 -0
  430. package/scripts/review/review-lint-sections.ts +80 -0
  431. package/scripts/review/review-lint.ts +221 -0
  432. package/scripts/review/review-prior-findings.ts +102 -0
  433. package/scripts/review/review-readers-scan.ts +4 -2
  434. package/scripts/review/review-record-cli.ts +177 -54
  435. package/scripts/review/review-record-code-round.ts +55 -0
  436. package/scripts/review/review-record-comment.ts +60 -0
  437. package/scripts/review/review-record-interaction.ts +35 -0
  438. package/scripts/review/review-record-pinned.ts +108 -0
  439. package/scripts/review/review-record.ts +25 -1
  440. package/scripts/review/review-reviewed-tree.ts +307 -0
  441. package/scripts/review/review-round-one-closure.ts +62 -0
  442. package/scripts/review/review-round-reuse.ts +106 -0
  443. package/scripts/review/review-round2-cli.ts +91 -29
  444. package/scripts/review/review-round2-skip-comment.ts +55 -0
  445. package/scripts/review/review-round2.ts +24 -8
  446. package/scripts/review/review-stamps.ts +7 -3
  447. package/scripts/review/review-stop-log.ts +20 -6
  448. package/scripts/review/review-stops-cli.ts +2 -1
  449. package/scripts/review/review-submit-cli.ts +101 -0
  450. package/scripts/rules/agent-model.ts +1 -1
  451. package/scripts/rules/bash-triggers.ts +3 -3
  452. package/scripts/rules/concise-reply.ts +88 -0
  453. package/scripts/rules/credential-body.ts +77 -0
  454. package/scripts/rules/decision-oracle.ts +1 -1
  455. package/scripts/rules/delivered-rules.ts +36 -17
  456. package/scripts/rules/destructive-command.ts +3 -11
  457. package/scripts/rules/direct-issue-comment.ts +34 -12
  458. package/scripts/rules/direct-issue-read.ts +191 -0
  459. package/scripts/rules/direct-pr-create.ts +4 -24
  460. package/scripts/rules/early-heartbeat.ts +2 -0
  461. package/scripts/rules/edit-tool.ts +27 -0
  462. package/scripts/rules/file-edit-rows.ts +17 -0
  463. package/scripts/rules/file-rewrite.ts +146 -0
  464. package/scripts/rules/firing-point.ts +3 -2
  465. package/scripts/rules/gh-api.ts +128 -7
  466. package/scripts/rules/gh-repo-selector.ts +84 -0
  467. package/scripts/rules/git-force.ts +2 -2
  468. package/scripts/rules/guard-remedy.ts +14 -5
  469. package/scripts/rules/implementation-cut.ts +61 -28
  470. package/scripts/rules/investigator-foreground.ts +231 -0
  471. package/scripts/rules/issue-citation.ts +15 -60
  472. package/scripts/rules/issue-comments.ts +9 -4
  473. package/scripts/rules/josh-git-bare.ts +1 -1
  474. package/scripts/rules/lane-background.ts +41 -13
  475. package/scripts/rules/lane-direct-tool.ts +107 -9
  476. package/scripts/rules/lane-interactive-ask.ts +4 -1
  477. package/scripts/rules/lane-park.ts +4 -2
  478. package/scripts/rules/lane-spilled-read.ts +90 -0
  479. package/scripts/rules/lane-split-park.ts +1 -1
  480. package/scripts/rules/lane-switch-main.ts +4 -4
  481. package/scripts/rules/last-prompt.ts +20 -47
  482. package/scripts/rules/oracle-consulted.ts +1 -1
  483. package/scripts/rules/permission-guards.ts +5 -1
  484. package/scripts/rules/piped-verification-shapes.ts +110 -0
  485. package/scripts/rules/piped-verification.ts +67 -17
  486. package/scripts/rules/poll-loop.ts +72 -9
  487. package/scripts/rules/pr-merge.ts +5 -5
  488. package/scripts/rules/pre-gate-cut.ts +3 -3
  489. package/scripts/rules/protected-files.ts +88 -24
  490. package/scripts/rules/reply-language.ts +3 -17
  491. package/scripts/rules/reply-prose.ts +34 -0
  492. package/scripts/rules/repo-admin.ts +132 -0
  493. package/scripts/rules/reviewer-foreground.ts +88 -0
  494. package/scripts/rules/round-one-check-related.ts +120 -0
  495. package/scripts/rules/rule-body-guard.ts +5 -15
  496. package/scripts/rules/rule-value-cache.ts +53 -5
  497. package/scripts/rules/rule-value-candidates.ts +84 -0
  498. package/scripts/rules/rule-value-cli.ts +59 -11
  499. package/scripts/rules/rule-value.ts +17 -4
  500. package/scripts/rules/run-tail-rule.ts +1 -1
  501. package/scripts/rules/sed-in-place.ts +196 -0
  502. package/scripts/rules/shell-segments.ts +10 -2
  503. package/scripts/rules/stop-rule-rows.ts +1 -13
  504. package/scripts/rules/stop-rules.ts +33 -58
  505. package/scripts/rules/test-declared-commit.ts +1 -1
  506. package/scripts/rules/third-party-write.ts +110 -23
  507. package/scripts/rules/timed-rerun.ts +1 -1
  508. package/scripts/rules/turn-filings.ts +90 -0
  509. package/scripts/rules/worktree-guard.ts +22 -7
  510. package/scripts/run/board/run-board-ending-reason.ts +6 -2
  511. package/scripts/run/board/run-board-fit.ts +24 -1
  512. package/scripts/run/board/run-board-found.ts +134 -0
  513. package/scripts/run/board/run-board-header.ts +8 -0
  514. package/scripts/run/board/run-board-labels.ts +11 -1
  515. package/scripts/run/board/run-board-lane-time.ts +1 -0
  516. package/scripts/run/board/run-board-link.ts +15 -2
  517. package/scripts/run/board/run-board-notes.ts +7 -1
  518. package/scripts/run/board/run-board-operation.ts +4 -2
  519. package/scripts/run/board/run-board-phase.ts +2 -5
  520. package/scripts/run/board/run-board-plan.ts +1 -1
  521. package/scripts/run/board/run-board-read.ts +46 -1
  522. package/scripts/run/board/run-board-reason.ts +20 -8
  523. package/scripts/run/board/run-board-render-checks.ts +3 -2
  524. package/scripts/run/board/run-board-render-drawn.ts +11 -0
  525. package/scripts/run/board/run-board-render-found.ts +157 -0
  526. package/scripts/run/board/run-board-render-lead.ts +9 -0
  527. package/scripts/run/board/run-board-render-legend.ts +19 -0
  528. package/scripts/run/board/run-board-render-tail.ts +1 -1
  529. package/scripts/run/board/run-board-render.ts +82 -18
  530. package/scripts/run/board/run-board-spin.ts +1 -3
  531. package/scripts/run/board/run-board-status.ts +2 -0
  532. package/scripts/run/board/run-board-tick.ts +39 -10
  533. package/scripts/run/carry/run-carry-ended.ts +3 -2
  534. package/scripts/run/carry/run-carry.ts +3 -2
  535. package/scripts/run/cut/run-cut-cli.ts +33 -133
  536. package/scripts/run/cut/run-cut-digest.ts +152 -0
  537. package/scripts/run/cut/run-cut-handoff.ts +52 -8
  538. package/scripts/run/cut/run-cut-report.ts +24 -3
  539. package/scripts/run/cut/run-cut-resume.ts +129 -0
  540. package/scripts/run/cut/run-cut-review.ts +105 -0
  541. package/scripts/run/cut/run-cut.ts +18 -7
  542. package/scripts/run/detached-launch.ts +1 -1
  543. package/scripts/run/entry/run-entry-cli.ts +88 -19
  544. package/scripts/run/entry/run-entry-spill.ts +83 -0
  545. package/scripts/run/entry/run-entry-stop.ts +5 -1
  546. package/scripts/run/entry/run-entry-trust.ts +91 -0
  547. package/scripts/run/event/run-event-baseline.ts +56 -0
  548. package/scripts/run/event/run-event-filed.ts +9 -3
  549. package/scripts/run/event/run-event-overrun.ts +197 -0
  550. package/scripts/run/event/run-event-render.ts +4 -0
  551. package/scripts/run/event/run-event-stream.ts +25 -3
  552. package/scripts/run/hold/run-stop-notify.ts +3 -8
  553. package/scripts/run/merge/run-merge-cap.ts +57 -0
  554. package/scripts/run/merge/run-merge-cli.ts +66 -23
  555. package/scripts/run/merge/run-merge-outcome.ts +2 -0
  556. package/scripts/run/merge/run-merge-ship.ts +13 -3
  557. package/scripts/run/merge/run-merge-signals.ts +40 -23
  558. package/scripts/run/merge/run-merge-steps.ts +56 -36
  559. package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
  560. package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
  561. package/scripts/run/merge/run-merge.ts +41 -14
  562. package/scripts/run/review/run-review-cli.ts +306 -20
  563. package/scripts/run/review/run-review-early-push.ts +121 -0
  564. package/scripts/run/review/run-review-join-read.ts +94 -0
  565. package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
  566. package/scripts/run/review/run-review-pinned-join.ts +216 -0
  567. package/scripts/run/review/run-review-pinned-steps.ts +383 -0
  568. package/scripts/run/review/run-review-pinned-tree.ts +37 -0
  569. package/scripts/run/review/run-review-pinned.ts +53 -0
  570. package/scripts/run/review/run-review-steps.ts +70 -34
  571. package/scripts/run/review/run-review.ts +26 -6
  572. package/scripts/run/run-parent-cut-hook.ts +3 -4
  573. package/scripts/run/run-prep.ts +1 -0
  574. package/scripts/run/run-stage-read.ts +2 -6
  575. package/scripts/run/run-step-cli.ts +17 -5
  576. package/scripts/run/run-step-lane-plan.ts +27 -0
  577. package/scripts/run/run-step-section.ts +26 -7
  578. package/scripts/run/run-step.ts +5 -2
  579. package/scripts/run/ship/run-ship-args.ts +19 -0
  580. package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
  581. package/scripts/run/ship/run-ship-cli.ts +131 -39
  582. package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
  583. package/scripts/run/ship/run-ship-detach.ts +10 -4
  584. package/scripts/run/ship/run-ship-handover.ts +106 -0
  585. package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
  586. package/scripts/run/ship/run-ship-lap.ts +63 -0
  587. package/scripts/run/ship/run-ship-next.ts +27 -26
  588. package/scripts/run/ship/run-ship-overlap.ts +8 -3
  589. package/scripts/run/ship/run-ship-place.ts +9 -2
  590. package/scripts/run/ship/run-ship-planned.ts +92 -0
  591. package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
  592. package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
  593. package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
  594. package/scripts/run/ship/run-ship-preflight.ts +16 -4
  595. package/scripts/run/ship/run-ship-probe.ts +23 -9
  596. package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
  597. package/scripts/run/ship/run-ship-relaunch.ts +51 -0
  598. package/scripts/run/ship/run-ship-return.ts +103 -29
  599. package/scripts/run/ship/run-ship-review-reason.ts +24 -0
  600. package/scripts/run/ship/run-ship-review-steps.ts +230 -59
  601. package/scripts/run/ship/run-ship-review.ts +171 -51
  602. package/scripts/run/ship/run-ship-reviewer.ts +138 -42
  603. package/scripts/run/ship/run-ship-scoped.ts +39 -10
  604. package/scripts/run/ship/run-ship-stage.ts +49 -13
  605. package/scripts/run/ship/run-ship-steps.ts +24 -4
  606. package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
  607. package/scripts/run/ship/run-ship-stop-context.ts +50 -13
  608. package/scripts/run/ship/run-ship-sync.ts +171 -30
  609. package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
  610. package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
  611. package/scripts/run/ship/run-ship-waiter.ts +114 -0
  612. package/scripts/run/ship/run-ship.ts +16 -2
  613. package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
  614. package/scripts/run/wake/run-wake-cli.ts +6 -113
  615. package/scripts/run/wake/run-wake-code.ts +53 -11
  616. package/scripts/run/wake/run-wake-driver.ts +5 -3
  617. package/scripts/run/wake/run-wake-finish.ts +137 -0
  618. package/scripts/run/wake/run-wake-loop.ts +5 -3
  619. package/scripts/run/wake/run-wake-ports.ts +11 -3
  620. package/scripts/run/wake/run-wake-session.ts +2 -0
  621. package/scripts/run/wake/run-wake.ts +3 -2
  622. package/scripts/safe-chain/ci-installer-pin.ts +49 -8
  623. package/scripts/safe-chain/project-config.ts +3 -2
  624. package/scripts/security/secretlint-scan.ts +42 -9
  625. package/scripts/security/security-audit-provision-main.ts +9 -1
  626. package/scripts/security/security-audit-provision.ts +1 -1
  627. package/scripts/security/security-audit.ts +21 -1
  628. package/scripts/security/sonar-file.ts +10 -5
  629. package/scripts/split/split-assess.ts +12 -3
  630. package/scripts/split/split-early-notice.ts +116 -0
  631. package/scripts/split/split-measure.ts +20 -2
  632. package/scripts/split/split-resolution.ts +107 -0
  633. package/scripts/test/direct-importers.ts +185 -0
  634. package/scripts/test/pure-files-entries.ts +83 -0
  635. package/scripts/test/related-heavy-tests.ts +81 -0
  636. package/scripts/test/test-no-base.ts +88 -0
  637. package/scripts/test/test-pinned-cache-logic.ts +363 -0
  638. package/scripts/test/test-pinned-cache.ts +337 -0
  639. package/scripts/test/test-pinned-emit.ts +49 -0
  640. package/scripts/test/test-pinned-functions.ts +116 -0
  641. package/scripts/test/test-pinned-ledger.ts +80 -0
  642. package/scripts/test/test-pinned-logic.ts +208 -20
  643. package/scripts/test/test-pinned-main.ts +84 -18
  644. package/scripts/test/test-pinned-pool.ts +73 -0
  645. package/scripts/test/test-pinned-probe.ts +254 -0
  646. package/scripts/test/test-pinned-prune-logic.ts +118 -0
  647. package/scripts/test/test-pinned-reach.ts +194 -0
  648. package/scripts/test/test-pinned-record.ts +196 -0
  649. package/scripts/test/test-pinned-rewire.ts +148 -0
  650. package/scripts/test/test-pinned-spawn.ts +81 -0
  651. package/scripts/test/test-pinned-stage.ts +70 -0
  652. package/scripts/test/test-pinned.ts +258 -47
  653. package/scripts/test/test-reach-cli.ts +61 -0
  654. package/scripts/test/test-read-map.ts +35 -11
  655. package/scripts/test/test-read-recording.ts +17 -5
  656. package/scripts/test/test-read-trace.ts +26 -8
  657. package/scripts/test/test-red-logic.ts +46 -7
  658. package/scripts/test/test-red.ts +34 -14
  659. package/scripts/test/test-related-claim.ts +37 -0
  660. package/scripts/test/test-related-main.ts +4 -1
  661. package/scripts/test/test-related-moved.ts +91 -0
  662. package/scripts/test/test-related-record.ts +43 -23
  663. package/scripts/test/test-related-scope.ts +99 -34
  664. package/scripts/test/test-related-synced.ts +135 -0
  665. package/scripts/test/test-related-touched.ts +84 -0
  666. package/scripts/test/test-related.ts +167 -83
  667. package/scripts/test/test-unit-guard.ts +28 -5
  668. package/scripts/test/unit-durations.ts +41 -0
  669. package/scripts/test/unit-worker-share.ts +5 -4
  670. package/scripts/test/vitest-cache-directory.ts +19 -0
  671. package/scripts/test/vitest-cli-excludes.ts +14 -0
  672. package/scripts/test/vitest-failure-summary.ts +111 -0
  673. package/scripts/test/whole-suite-files.ts +12 -1
  674. package/scripts/time-runtime/time-batch-guard.ts +42 -4
  675. package/scripts/time-runtime/time-bundle-call.ts +1 -1
  676. package/scripts/time-runtime/time-bundles.ts +36 -5
  677. package/scripts/time-runtime/time-markers.ts +2 -2
  678. package/scripts/time-runtime/time-search-call.ts +38 -0
  679. package/scripts/time-runtime/time-shell.ts +1 -1
  680. package/scripts/time-runtime/time-transcript-line.ts +17 -5
  681. package/scripts/version/development-engines-verdict.ts +124 -0
  682. package/scripts/version/latest-corepack.ts +2 -0
  683. package/templates/gitignore +2 -0
  684. package/templates/workflows/ci.yml +41 -0
  685. package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
  686. package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
  687. package/dist/hooks/chunk-TUIFS5E7.js +0 -72
  688. package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
  689. package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
  690. package/scripts/rules/filing-cap.ts +0 -139
@@ -1,5 +1,6 @@
1
1
  import { issue_bug_label } from './issue-bug-label'
2
2
  import { markdown_section } from './markdown-section'
3
+ import { reproduction_measure } from './reproduction-measure'
3
4
 
4
5
  // The origin line a bug body declares under its background heading: the merged pull request that
5
6
  // introduced the defect, or "unknown" with the reason it could not be traced. `defect:report` groups
@@ -12,6 +13,8 @@ const MISSING_ORIGIN =
12
13
  'missing origin line: a `- 種別: 不具合` body declares `- 原因: #<PR>` or `- 原因: 不明(<reason>)` under ## 背景 (`pnpm josh defect:origin <N>` lists candidates)'
13
14
  const MALFORMED_ORIGIN =
14
15
  'malformed origin line: write `- 原因: #<PR>` or `- 原因: 不明(<reason>)` with a non-empty reason'
16
+ const MISSING_REPRODUCTION =
17
+ 'missing reproduction: a `- 原因: 不明(<reason>)` bug carries ## 再現 with a backticked command and its actual output in a fenced block'
15
18
 
16
19
  type DefectOrigin = { kind: 'pr'; pr: number } | { kind: 'unknown'; reason: string }
17
20
 
@@ -54,7 +57,22 @@ function problem(body: string): string | undefined {
54
57
  return lines.every((line) => parse_line(line) !== undefined) ? undefined : MALFORMED_ORIGIN
55
58
  }
56
59
 
57
- const defect_origin_line = { MALFORMED_ORIGIN, MISSING_ORIGIN, parse, problem }
60
+ // An untraced bug owes the lane a way to see it: without a reproduction the run starts by
61
+ // investigating. A traced one (a pull request origin) already names where to look, so it owes none.
62
+ function reproduction_problem(body: string): string | undefined {
63
+ if (!issue_bug_label.is_bug_fix(body) || parse(body)?.kind !== 'unknown') return undefined
64
+
65
+ return reproduction_measure.has_command_output(body) ? undefined : MISSING_REPRODUCTION
66
+ }
67
+
68
+ const defect_origin_line = {
69
+ MALFORMED_ORIGIN,
70
+ MISSING_ORIGIN,
71
+ MISSING_REPRODUCTION,
72
+ parse,
73
+ problem,
74
+ reproduction_problem,
75
+ }
58
76
 
59
77
  export type { DefectOrigin }
60
78
  export { defect_origin_line }
@@ -1,19 +1,8 @@
1
- import { git_gh_exec } from '#scripts/gh/git-gh-exec'
2
- import { issue_label_schema } from '#scripts/git/git-schemas'
3
- import { parse_json } from '#scripts/git/parse-json-array'
4
1
  import { shared_cache, type CacheSlot } from '#scripts/josh/shared-cache'
5
- import { bounded_pool } from '#scripts/lib/bounded-pool'
6
2
  import { z } from 'zod'
7
- import { defect_rate, type DefectRate, type RateIssue } from './defect-rate'
3
+ import { defect_rate, type DefectRate } from './defect-rate'
4
+ import { issue_search } from './issue-search'
8
5
 
9
- const PER_PAGE = '100'
10
- const FIRST_PAGE = 1
11
- const SECOND_PAGE = 2
12
- // The search API serves at most this many results, so a window past it holds no further page.
13
- const SEARCH_RESULT_CEILING = 1000
14
- // The search API allows 30 requests a minute; both searches together stay at the ~10 requests the
15
- // sequential walk made, so this bounds only how many are in flight at once.
16
- const PAGE_CONCURRENCY = 4
17
6
  const MS_PER_MINUTE = 60_000
18
7
  const CACHE_TTL_MINUTES = 15
19
8
  // A 14-day rate does not move within a quarter of an hour, and every `backlog:next` / `backlog:plan`
@@ -32,100 +21,14 @@ const defect_rate_schema = z.object({
32
21
  is_capped: z.boolean(),
33
22
  })
34
23
 
35
- const search_item_schema = z.object({
36
- body: z.string().nullish(),
37
- labels: z.array(issue_label_schema),
38
- })
39
-
40
- // One page of `search/issues`.
41
- const search_page_schema = z.object({
42
- total_count: z.number(),
43
- incomplete_results: z.boolean(),
44
- items: z.array(search_item_schema),
45
- })
46
-
47
- type SearchPage = z.infer<typeof search_page_schema>
48
-
49
- interface SearchResult {
50
- issues: ReadonlyArray<RateIssue>
51
- is_capped: boolean
52
- }
53
-
54
- function search_path(query: string, page: number = FIRST_PAGE): string {
55
- const parameters = new URLSearchParams({ q: query, per_page: PER_PAGE })
56
-
57
- if (page !== FIRST_PAGE) parameters.set('page', String(page))
58
-
59
- return `search/issues?${parameters.toString()}`
60
- }
61
-
62
- // How many pages the search holds, read off the first page's own count. Bounded by the API's
63
- // ceiling, past which GitHub serves no page at all — so this asks for exactly the pages `gh api
64
- // --paginate` would walk to.
65
- function page_count(first: SearchPage): number {
66
- return Math.ceil(Math.min(first.total_count, SEARCH_RESULT_CEILING) / Number(PER_PAGE))
67
- }
68
-
69
- async function read_page(query: string, page: number): Promise<SearchPage | undefined> {
70
- const raw = await git_gh_exec.exec_gh_api({ path: search_path(query, page) })
71
-
72
- return parse_json.parse_json_object_or_undefined(raw, search_page_schema)
73
- }
74
-
75
- // Every page of one search. Not `gh api --paginate`: it follows each page's `next` link one page
76
- // after another, at about 1.5 s a page, on every `backlog:plan`. The first page names the total, so
77
- // the rest are fetched together; any page that does not read fails the whole search.
78
- async function read_pages(query: string): Promise<ReadonlyArray<SearchPage> | undefined> {
79
- const first = await read_page(query, FIRST_PAGE)
80
-
81
- if (first === undefined) return undefined
82
-
83
- const rest_numbers = Array.from(
84
- { length: Math.max(page_count(first) - FIRST_PAGE, 0) },
85
- (_value, index) => index + SECOND_PAGE,
86
- )
87
- const rest = await bounded_pool.bounded_map(
88
- rest_numbers,
89
- PAGE_CONCURRENCY,
90
- async (page) => await read_page(query, page),
91
- )
92
-
93
- return rest.every((page) => page !== undefined) ? [first, ...rest] : undefined
94
- }
95
-
96
- function to_result(pages: ReadonlyArray<SearchPage>): SearchResult {
97
- const issues = pages.flatMap((page) =>
98
- page.items.map((item) => ({
99
- body: item.body ?? '',
100
- labels: item.labels.map((label) => label.name),
101
- })),
102
- )
103
- // The search API serves at most 1000 results, and says so only by a total larger than what it
104
- // handed back — a window past that ceiling is measured on part of itself.
105
- const total = pages[0]?.total_count ?? 0
106
- const is_incomplete = pages.some((page) => page.incomplete_results)
107
-
108
- return { issues, is_capped: is_incomplete || total > issues.length }
109
- }
110
-
111
- async function search(query: string): Promise<SearchResult | undefined> {
112
- try {
113
- const pages = await read_pages(query)
114
-
115
- return pages === undefined ? undefined : to_result(pages)
116
- } catch {
117
- return undefined
118
- }
119
- }
120
-
121
24
  async function measure_uncached(
122
25
  repo: string,
123
26
  days: number,
124
27
  since: string,
125
28
  ): Promise<DefectRate | undefined> {
126
29
  const [filed, completed] = await Promise.all([
127
- search(defect_rate.filed_query(repo, since)),
128
- search(defect_rate.completed_query(repo, since)),
30
+ issue_search.search(defect_rate.filed_query(repo, since)),
31
+ issue_search.search(defect_rate.completed_query(repo, since)),
129
32
  ])
130
33
 
131
34
  if (filed === undefined || completed === undefined) return undefined
@@ -158,6 +61,6 @@ async function measure_window(
158
61
  )
159
62
  }
160
63
 
161
- const defect_rate_cli = { RATE_CACHE, search_path, measure_window }
64
+ const defect_rate_cli = { RATE_CACHE, measure_window }
162
65
 
163
66
  export { defect_rate_cli }
@@ -1,3 +1,4 @@
1
+ import { run_record_path } from '#scripts/josh/run-record-path'
1
2
  import { defect_origin_line } from './defect-origin-line'
2
3
 
3
4
  // Defects grouped by the pull request each one's origin line names: how many per pull request, per
@@ -14,9 +15,6 @@ const SCRIPT_AREA_DEPTH = 2
14
15
  const ROOT_AREA = '(root)'
15
16
  const UNREAD_AREA = '(pull request not read)'
16
17
  const TEST_FILE = /\.(?:test|e2e)\.ts$|\.snap$/u
17
- // The run's own records — observations, accepted metrics — which nearly every merge adds, so they
18
- // say nothing about what the pull request changed.
19
- const RUN_RECORD_ROOT = '.josh/'
20
18
 
21
19
  type ChangeKind = 'add' | 'fix' | 'remove'
22
20
 
@@ -54,8 +52,10 @@ interface DefectReport {
54
52
  added_within_week: ReadonlyMap<number, number>
55
53
  }
56
54
 
55
+ // The run's own records, which nearly every merge adds, say nothing about what the pull request
56
+ // changed.
57
57
  function changed(files: ReadonlyArray<PrFile>): ReadonlyArray<PrFile> {
58
- return files.filter((file) => !file.filename.startsWith(RUN_RECORD_ROOT))
58
+ return files.filter((file) => !run_record_path.is_run_record(file.filename))
59
59
  }
60
60
 
61
61
  // A pull request that brought in a new non-test file added a mechanism; one that took out more than it
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env tsx
2
+ import { writeFileSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import path from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { git_gh_command } from '#scripts/gh/git-gh-command'
7
+ import { bounded_pool } from '#scripts/lib/bounded-pool'
8
+ import { cli_flags } from '#scripts/lib/cli-flags'
9
+ import { defect_rate } from './defect-rate'
10
+ import { effect_audit, type AuditRow, type FollowUp } from './effect-audit'
11
+ import { issue_sample_prime } from './issue-sample-prime'
12
+ import { issue_search, type SearchIssue } from './issue-search'
13
+ import { session_cite } from './session-cite'
14
+
15
+ // `josh effect:audit [--days <N>] [--draft <N>]` — every run-time-shortening issue closed in the window,
16
+ // one line each: its expected effect, its follow-up measurements and their verdict (`effect-audit.ts`).
17
+ // `--draft <N>` writes the retirement candidate for an issue whose measurement fell short and prints the
18
+ // `issue:file` line that files it once its placeholders are filled.
19
+
20
+ const ARGV_OFFSET = 2
21
+ const FAILURE_EXIT_CODE = 1
22
+ const DEFAULT_DAYS = 30
23
+ const PULL_CONCURRENCY = 4
24
+ const USAGE = 'Usage: josh effect:audit [--days <N>] [--draft <issue-number>]'
25
+ const OPTIONS = { days: { type: 'string' }, draft: { type: 'string' } } as const
26
+
27
+ interface AuditRequest {
28
+ days: number
29
+ draft: number | undefined
30
+ }
31
+
32
+ interface Audit {
33
+ rows: ReadonlyArray<AuditRow>
34
+ since: string
35
+ is_capped: boolean
36
+ unattributed: number
37
+ }
38
+
39
+ interface CitedFollowUp {
40
+ issue: SearchIssue
41
+ pr: number
42
+ }
43
+
44
+ function positive(text: string | undefined): number | undefined {
45
+ const value = Number(text)
46
+
47
+ return Number.isSafeInteger(value) && value > 0 ? value : undefined
48
+ }
49
+
50
+ function with_draft(days: number, text: string | undefined): AuditRequest | undefined {
51
+ if (text === undefined) return { days, draft: undefined }
52
+ const draft = positive(text)
53
+
54
+ return draft === undefined ? undefined : { days, draft }
55
+ }
56
+
57
+ function parse_request(argv: ReadonlyArray<string>): AuditRequest | undefined {
58
+ const parsed = cli_flags.values_of(argv, OPTIONS)
59
+ if (parsed === undefined) return undefined
60
+ const days = positive(parsed.days ?? String(DEFAULT_DAYS))
61
+
62
+ return days === undefined ? undefined : with_draft(days, parsed.draft)
63
+ }
64
+
65
+ // The issue the cited pull request closes; an unreadable pull request leaves the follow-up unattributed,
66
+ // and the listing counts it.
67
+ async function measured_issue(pr: number): Promise<number | undefined> {
68
+ try {
69
+ const pull = await issue_sample_prime.read_source(pr)
70
+
71
+ return pull.closes
72
+ } catch {
73
+ return undefined
74
+ }
75
+ }
76
+
77
+ // Each cited pull request is read once, a few at a time: the follow-up search spans every date.
78
+ async function closes_by_pull(
79
+ prs: ReadonlyArray<number>,
80
+ ): Promise<ReadonlyMap<number, number | undefined>> {
81
+ const unique = [...new Set(prs)]
82
+ const closes = await bounded_pool.bounded_map(unique, PULL_CONCURRENCY, measured_issue)
83
+
84
+ return new Map(unique.map((pr, index) => [pr, closes[index]]))
85
+ }
86
+
87
+ // A follow-up citing the issue form names what it measures itself; only a cited pull request is read.
88
+ async function follow_ups_of(candidates: ReadonlyArray<SearchIssue>): Promise<Array<FollowUp>> {
89
+ const cited = candidates
90
+ .map((issue) => ({ issue, pr: effect_audit.cited_pull(issue) }))
91
+ .filter((entry): entry is CitedFollowUp => entry.pr !== undefined)
92
+ const closes = await closes_by_pull(cited.map(({ pr }) => pr))
93
+ const by_issue = candidates
94
+ .map((issue) => ({ issue, measures: effect_audit.cited_issue(issue) }))
95
+ .filter(({ measures }) => measures !== undefined)
96
+
97
+ return [...cited.map(({ issue, pr }) => ({ issue, measures: closes.get(pr) })), ...by_issue]
98
+ }
99
+
100
+ async function read_audit(repo: string, days: number, now_ms: number): Promise<Audit | undefined> {
101
+ const since = defect_rate.window_start(now_ms, days)
102
+ const [origins, follow_ups] = await Promise.all([
103
+ issue_search.search(effect_audit.origin_query(repo, since)),
104
+ issue_search.search(effect_audit.follow_up_query(repo)),
105
+ ])
106
+
107
+ if (origins === undefined || follow_ups === undefined) return undefined
108
+
109
+ const linked = await follow_ups_of(follow_ups.issues)
110
+ const rows = effect_audit.rows(origins.issues, linked)
111
+ const unattributed = linked.filter((follow_up) => follow_up.measures === undefined).length
112
+
113
+ return { rows, since, is_capped: origins.is_capped || follow_ups.is_capped, unattributed }
114
+ }
115
+
116
+ function print_audit(audit: Audit): number {
117
+ console.info(effect_audit.summary(audit.rows, audit.since))
118
+ for (const row of audit.rows) console.info(session_cite.text(effect_audit.format_row(row)))
119
+ if (audit.is_capped) console.info('(capped: the search served part of the window only)')
120
+
121
+ if (audit.unattributed > 0) {
122
+ console.info(
123
+ `(${String(audit.unattributed)} follow-ups measure no issue: their pull request could not be read or closes none)`,
124
+ )
125
+ }
126
+
127
+ return 0
128
+ }
129
+
130
+ function print_draft(rows: ReadonlyArray<AuditRow>, issue: number): number {
131
+ const row = rows.find((candidate) => candidate.origin.number === issue)
132
+
133
+ if (row?.result !== 'unmet') {
134
+ console.error(
135
+ `${session_cite.issue(issue)} is not an issue of the window whose measurement fell short`,
136
+ )
137
+
138
+ return FAILURE_EXIT_CODE
139
+ }
140
+
141
+ const file = path.join(tmpdir(), `josh-effect-retire-${String(issue)}.md`)
142
+
143
+ writeFileSync(file, effect_audit.draft_body(row))
144
+ console.info(`Fill in the <...> lines of ${file}, then file it:`)
145
+ console.info(
146
+ `pnpm josh issue:file "${effect_audit.draft_title(row)}" --body-file ${file} --label run:lane`,
147
+ )
148
+
149
+ return 0
150
+ }
151
+
152
+ async function audit_of(request: AuditRequest, now_ms: number): Promise<Audit | undefined> {
153
+ const repo = await git_gh_command.repo_get_name_with_owner()
154
+
155
+ return repo === undefined ? undefined : await read_audit(repo, request.days, now_ms)
156
+ }
157
+
158
+ async function run(argv: ReadonlyArray<string>, now_ms: number = Date.now()): Promise<number> {
159
+ const request = parse_request(argv)
160
+ if (request === undefined) return (console.error(USAGE), FAILURE_EXIT_CODE)
161
+ const audit = await audit_of(request, now_ms)
162
+
163
+ if (audit === undefined) {
164
+ return (console.error('effect:audit: the issue search could not be read'), FAILURE_EXIT_CODE)
165
+ }
166
+
167
+ return request.draft === undefined ? print_audit(audit) : print_draft(audit.rows, request.draft)
168
+ }
169
+
170
+ async function main(argv: ReadonlyArray<string>): Promise<void> {
171
+ process.exitCode = await run(argv)
172
+ }
173
+
174
+ const effect_audit_cli = { run }
175
+
176
+ if (process.argv[1] === fileURLToPath(import.meta.url)) await main(process.argv.slice(ARGV_OFFSET))
177
+
178
+ export { effect_audit_cli }
@@ -0,0 +1,185 @@
1
+ import { expected_effect_lint } from './expected-effect-lint'
2
+ import { issue_cite } from './issue-cite'
3
+ import { has_label_name, MEASURED_LABEL, MEASURED_UNMET_LABEL } from './issue-labels'
4
+ import { issue_sample_gate } from './issue-sample-gate'
5
+ import type { SearchIssue } from './issue-search'
6
+
7
+ // joshuafolkken/kit#4958: a run-time-shortening issue states its expected effect at filing
8
+ // (`expected-effect-lint.ts`), and nothing read it again once merged. This sets each closed one beside
9
+ // the follow-up measurements of it — issues whose run condition names the pull request that closed it
10
+ // — and their recorded verdict: `measured:unmet` fell short, `measured` alone reached it, anything else
11
+ // is not measured yet. A shortfall is drafted as a retirement candidate for `issue:file`.
12
+
13
+ const EFFECT_LABELS: ReadonlyArray<string> = ['効く率', '1 回あたりの得', '外れたときの損']
14
+ const NO_VALUE = '—'
15
+ const RESULTS = ['met', 'unmet', 'unmeasured'] as const
16
+
17
+ type EffectResult = (typeof RESULTS)[number]
18
+
19
+ interface AuditRow {
20
+ origin: SearchIssue
21
+ follow_ups: ReadonlyArray<SearchIssue>
22
+ result: EffectResult
23
+ }
24
+
25
+ // A follow-up and the issue it measures: the one its run condition's pull request closes. A body
26
+ // naming other issues (a parent, lanes it compares against) measures none of them.
27
+ interface FollowUp {
28
+ issue: SearchIssue
29
+ measures: number | undefined
30
+ }
31
+
32
+ const RESULT_WORDS: Readonly<Record<EffectResult, string>> = {
33
+ met: '届いた',
34
+ unmet: '届かなかった',
35
+ unmeasured: '未計測',
36
+ }
37
+
38
+ // Plain: the rows feed an issue body as well as the session, which links them where it prints them.
39
+ function cite(issue: SearchIssue): string {
40
+ return issue_cite.plain(issue.number)
41
+ }
42
+
43
+ // The closed time-shortening issues of the window. The search narrows by the declaration's words; the
44
+ // exact line under the background heading is checked on each body, since the search matches the
45
+ // follow-ups' own "confirm the shortening" aim too.
46
+ function origin_query(repo: string, since: string): string {
47
+ return `repo:${repo} is:issue is:closed reason:completed closed:>=${since} "目的: 時間短縮" in:body`
48
+ }
49
+
50
+ // Every follow-up measurement, of any date: one may be filed before or long after its origin closed.
51
+ function follow_up_query(repo: string): string {
52
+ return `repo:${repo} is:issue "実施可能条件" in:body`
53
+ }
54
+
55
+ // The pull request a follow-up's run condition names, or `undefined` for an issue that declares none
56
+ // or cites the issue whose closing pull request it waits on.
57
+ function cited_pull(issue: SearchIssue): number | undefined {
58
+ return issue_sample_gate.cited_via(issue.body, 'pull')
59
+ }
60
+
61
+ // The issue a follow-up's run condition names by the issue form — the issue it measures, unread.
62
+ function cited_issue(issue: SearchIssue): number | undefined {
63
+ return issue_sample_gate.cited_via(issue.body, 'issue')
64
+ }
65
+
66
+ // One shortfall outweighs any reach: a mechanism some measurement found wanting is the one to look at.
67
+ function result_of(follow_ups: ReadonlyArray<SearchIssue>): EffectResult {
68
+ if (follow_ups.some((issue) => has_label_name(issue.labels, MEASURED_UNMET_LABEL))) return 'unmet'
69
+
70
+ return follow_ups.some((issue) => has_label_name(issue.labels, MEASURED_LABEL))
71
+ ? 'met'
72
+ : 'unmeasured'
73
+ }
74
+
75
+ function row_of(origin: SearchIssue, follow_ups: ReadonlyArray<FollowUp>): AuditRow {
76
+ const measuring = follow_ups
77
+ .filter((follow_up) => follow_up.measures === origin.number)
78
+ .map((follow_up) => follow_up.issue)
79
+
80
+ return { origin, follow_ups: measuring, result: result_of(measuring) }
81
+ }
82
+
83
+ function rows(
84
+ origins: ReadonlyArray<SearchIssue>,
85
+ follow_ups: ReadonlyArray<FollowUp>,
86
+ ): ReadonlyArray<AuditRow> {
87
+ return origins
88
+ .filter((origin) => expected_effect_lint.is_target(origin.body))
89
+ .map((origin) => row_of(origin, follow_ups))
90
+ }
91
+
92
+ function effect_text(body: string): string {
93
+ return EFFECT_LABELS.map(
94
+ (label) => `${label} ${expected_effect_lint.value_of(body, label) ?? NO_VALUE}`,
95
+ ).join(' · ')
96
+ }
97
+
98
+ function follow_up_text(follow_ups: ReadonlyArray<SearchIssue>): string {
99
+ if (follow_ups.length === 0) return 'なし'
100
+
101
+ return follow_ups.map((issue) => `${cite(issue)} (${issue.state})`).join(', ')
102
+ }
103
+
104
+ function format_row(row: AuditRow): string {
105
+ const effect = `期待効果: ${effect_text(row.origin.body)}`
106
+ const measured = `後続計測: ${follow_up_text(row.follow_ups)}`
107
+
108
+ return `${cite(row.origin)} — ${effect} — ${measured} — 結果: ${RESULT_WORDS[row.result]}`
109
+ }
110
+
111
+ function tally(audit_rows: ReadonlyArray<AuditRow>, result: EffectResult): string {
112
+ return `${RESULT_WORDS[result]} ${String(audit_rows.filter((row) => row.result === result).length)}`
113
+ }
114
+
115
+ function summary(audit_rows: ReadonlyArray<AuditRow>, since: string): string {
116
+ const tallies = RESULTS.map((result) => tally(audit_rows, result)).join(' · ')
117
+
118
+ return `effect:audit — closed since ${since}: ${String(audit_rows.length)} issues · ${tallies}`
119
+ }
120
+
121
+ function unmet_measurements(row: AuditRow): string {
122
+ return row.follow_ups
123
+ .filter((issue) => has_label_name(issue.labels, MEASURED_UNMET_LABEL))
124
+ .map((issue) => cite(issue))
125
+ .join(', ')
126
+ }
127
+
128
+ function draft_title(row: AuditRow): string {
129
+ return `Retire or shrink the mechanism of ${cite(row.origin)}, which fell short of its expected effect`
130
+ }
131
+
132
+ function background_lines(origin: string, measurements: string): ReadonlyArray<string> {
133
+ return [
134
+ '## 背景',
135
+ '',
136
+ '- 種別: 非不具合',
137
+ '- 目的: 機能改善',
138
+ '- 人の判断: 不要',
139
+ '- 優先度: 通常',
140
+ '- 規模見積もり: 約 <N> ファイル / <M> 行(テスト除く)',
141
+ `- 出どころ: ${measurements} の計測(\`measured:unmet\`)`,
142
+ '',
143
+ `${origin} で足した機構は、後続計測 ${measurements} で期待効果に届かなかった。効かない機構は利用者の実行時間・読む量・保守の負担を増やすので、撤去するか、効く範囲に縮小する。`,
144
+ ]
145
+ }
146
+
147
+ // The retirement candidate's body. The scale line is what only the filer knows: `issue:lint` refuses
148
+ // it until it is filled, and `issue:file`'s scout stops a duplicate.
149
+ function draft_body(row: AuditRow): string {
150
+ const origin = cite(row.origin)
151
+ const measurements = unmet_measurements(row)
152
+
153
+ return [
154
+ ...background_lines(origin, measurements),
155
+ '',
156
+ '## 現象',
157
+ '',
158
+ `- ${origin} の期待効果: ${effect_text(row.origin.body)}`,
159
+ `- 計測 ${measurements} の結果: 届かなかった`,
160
+ '',
161
+ '## 期待結果',
162
+ '',
163
+ `- ${origin} で足した機構が撤去されているか、効く範囲に縮小されている`,
164
+ '',
165
+ '## 受け入れ条件',
166
+ '',
167
+ `- [ ] ${origin} で足した機構を撤去するか縮小し、どちらを選んだかとその理由を記録する`,
168
+ '',
169
+ ].join('\n')
170
+ }
171
+
172
+ const effect_audit = {
173
+ cited_issue,
174
+ cited_pull,
175
+ draft_body,
176
+ draft_title,
177
+ follow_up_query,
178
+ format_row,
179
+ origin_query,
180
+ rows,
181
+ summary,
182
+ }
183
+
184
+ export type { AuditRow, EffectResult, FollowUp }
185
+ export { effect_audit }
@@ -9,7 +9,18 @@ import { markdown_section } from './markdown-section'
9
9
  const DECLARATION_LINE = '- 目的: 時間短縮'
10
10
  const BACKGROUND_HEADING = '## 背景'
11
11
  const HEADING = '## 期待効果'
12
- const ITEM_LABELS: ReadonlyArray<string> = ['効く率', '1 回あたりの得', '外れたときの損', '出典']
12
+ const RATE_LABEL = '効く率'
13
+ const GAIN_LABEL = '1 回あたりの得'
14
+ const ITEM_LABELS: ReadonlyArray<string> = [RATE_LABEL, GAIN_LABEL, '外れたときの損', '出典']
15
+ // The numbers the backlog ranking reads off the rate and the gain (joshuafolkken/kit#5302).
16
+ const WEEK = '週'
17
+ const COUNT = /(?<![\d.])(?<count>\d+) ?回/gu
18
+ const SPAN = /(?<![\d.])(?<weeks>\d+) ?週/u
19
+ const ONE_WEEK = 1
20
+ const DURATION = /(?<![\d.])(?<amount>\d+(?:\.\d+)?) ?(?<unit>[分秒])/u
21
+ const SECONDS_UNIT = '秒'
22
+ const SECONDS_PER_MINUTE = 60
23
+ const NONE = 0
13
24
  // A template placeholder value — the `<...>` guidance a filed Issue replaces. It is not an estimate, but a
14
25
  // value that only opens with `<` (an upper bound such as `<1 min`) is one, so the placeholder must close with `>` too.
15
26
  const PLACEHOLDER = /^<.*>$/u
@@ -29,22 +40,64 @@ function item_value(line: string, label: string): string | undefined {
29
40
  return line.slice(prefix.length).trim()
30
41
  }
31
42
 
32
- function has_item(lines: ReadonlyArray<string>, label: string): boolean {
33
- return lines.some((line) => {
34
- const value = item_value(line.trim(), label)
43
+ function is_stated(value: string | undefined): value is string {
44
+ return value !== undefined && value.length > 0 && !PLACEHOLDER.test(value)
45
+ }
46
+
47
+ function stated_value(lines: ReadonlyArray<string>, label: string): string | undefined {
48
+ return lines.map((line) => item_value(line.trim(), label)).find((value) => is_stated(value))
49
+ }
50
+
51
+ function effect_lines(body: string): ReadonlyArray<string> {
52
+ const section = markdown_section.section_lines(body, HEADING).join('\n')
53
+
54
+ return markdown_section.unfenced_lines(section)
55
+ }
56
+
57
+ // The value the body's expected-effect section states for one item, or undefined when it states none.
58
+ function value_of(body: string, label: string): string | undefined {
59
+ return stated_value(effect_lines(body), label)
60
+ }
61
+
62
+ // The weeks a rate is counted over: its stated span ("10 in 2 weeks", "1 every 2 weeks"), else one.
63
+ function weeks_of(rate: string): number {
64
+ const weeks = SPAN.exec(rate)?.groups?.['weeks']
35
65
 
36
- return value !== undefined && value.length > 0 && !PLACEHOLDER.test(value)
37
- })
66
+ return Math.max(Number(weeks ?? ONE_WEEK), ONE_WEEK)
67
+ }
68
+
69
+ // The hits a week a rate states: its last count, so "5 of 23 a week" reads the 5 the speed-up applies to,
70
+ // over its span of weeks. `0` when the rate names no week or no count.
71
+ function weekly_hits(rate: string | undefined): number {
72
+ if (!rate?.includes(WEEK)) return NONE
73
+ const counts = [...rate.matchAll(COUNT)].map((match) => Number(match.groups?.['count']))
74
+
75
+ return (counts.at(-1) ?? NONE) / weeks_of(rate)
76
+ }
77
+
78
+ // The minutes one hit saves: the first number the gain states in seconds or minutes — a range written
79
+ // with one closing unit ("3-6 min") reads its upper bound. `0` when it states none.
80
+ function minutes_per_hit(gain = ''): number {
81
+ const groups = DURATION.exec(gain)?.groups
82
+ if (groups === undefined) return NONE
83
+ const amount = Number(groups['amount'])
84
+
85
+ return groups['unit'] === SECONDS_UNIT ? amount / SECONDS_PER_MINUTE : amount
86
+ }
87
+
88
+ // The minutes a week the declared speed-up saves — hits a week times the gain per hit — on the same
89
+ // minute scale as `issue_priority.lost_minutes`. `0` when the rate or the gain is not a number.
90
+ function weekly_minutes(body = ''): number {
91
+ return weekly_hits(value_of(body, RATE_LABEL)) * minutes_per_hit(value_of(body, GAIN_LABEL))
38
92
  }
39
93
 
40
94
  // Every expected-effect problem a body has, or an empty array. A body without the declaration has none.
41
95
  function problems(body: string): ReadonlyArray<string> {
42
96
  if (!is_target(body)) return []
43
97
  if (!markdown_section.has_line(body, HEADING)) return [`missing heading: ${HEADING}`]
44
- const section = markdown_section.section_lines(body, HEADING).join('\n')
45
- const lines = markdown_section.unfenced_lines(section)
98
+ const lines = effect_lines(body)
46
99
 
47
- return ITEM_LABELS.filter((label) => !has_item(lines, label)).map(
100
+ return ITEM_LABELS.filter((label) => stated_value(lines, label) === undefined).map(
48
101
  (label) => `missing expected-effect item: - ${label}: <value>`,
49
102
  )
50
103
  }
@@ -55,6 +108,8 @@ const expected_effect_lint = {
55
108
  ITEM_LABELS,
56
109
  is_target,
57
110
  problems,
111
+ value_of,
112
+ weekly_minutes,
58
113
  }
59
114
 
60
115
  export { expected_effect_lint }
@@ -1,7 +1,9 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
2
  import { open_issue_schema, type OpenIssueData } from '#scripts/git/git-schemas'
3
3
  import { parse_json_array_safe } from '#scripts/git/parse-json-array'
4
+ import { expected_effect_lint } from './expected-effect-lint'
4
5
  import { has_any_label, NOT_DIRECTLY_RUNNABLE_LABELS } from './issue-labels'
6
+ import { issue_priority } from './issue-priority'
5
7
  import { issue_rank, type DependencyRow } from './issue-rank'
6
8
  import { session_cite } from './session-cite'
7
9
 
@@ -63,7 +65,10 @@ function order(
63
65
  const dependents = issue_rank.count_dependents(candidates.map((issue) => dependency_row(issue)))
64
66
 
65
67
  return issue_rank.rank(candidates, (issue) => ({
68
+ is_last: issue_priority.is_last(issue.body),
66
69
  labels: (issue.labels ?? []).map((label) => label.name),
70
+ lost_minutes: issue_priority.lost_minutes(issue.body),
71
+ gain_minutes: expected_effect_lint.weekly_minutes(issue.body),
67
72
  dependents: dependents.get(String(issue.number)) ?? 0,
68
73
  }))
69
74
  }