@joshuafolkken/kit 1.1784.0 → 1.2114.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (690) hide show
  1. package/.claude/agents/reviewer-verification.md +19 -0
  2. package/.claude/agents/reviewer.md +19 -0
  3. package/.claude/settings.json +5 -6
  4. package/.claude/skills/dependency-update/SKILL.md +9 -10
  5. package/.claude/skills/workflow-commands/SKILL.md +6 -6
  6. package/.claude/skills/workflow-commands/background-commands.md +8 -5
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  13. package/.claude/skills/workflow-commands/backlogrun.md +1 -1
  14. package/.claude/skills/workflow-commands/chain-rule.md +41 -32
  15. package/.claude/skills/workflow-commands/delegation.md +6 -6
  16. package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
  17. package/.claude/skills/workflow-commands/followup.md +7 -5
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
  19. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  20. package/.claude/skills/workflow-commands/issue-comments.md +9 -4
  21. package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +2 -2
  23. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  24. package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
  25. package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
  26. package/.claude/skills/workflow-commands/prerequisite.md +3 -2
  27. package/.claude/skills/workflow-commands/retrospective.md +2 -2
  28. package/.claude/skills/workflow-commands/split-assessment.md +7 -5
  29. package/.codex/hooks.json +5 -5
  30. package/.github/actions/setup-pnpm/action.yml +9 -16
  31. package/.github/dependabot.yml +16 -0
  32. package/.github/workflows/auto-tag.yml +45 -0
  33. package/.github/workflows/ci.yml +155 -15
  34. package/.github/workflows/dependabot-auto-merge.yml +63 -4
  35. package/.github/workflows/production.yml +8 -0
  36. package/.github/workflows/publish.yml +59 -45
  37. package/.vscode/settings.json +19 -0
  38. package/CLAUDE.md +5 -12
  39. package/SECURITY.md +2 -2
  40. package/dist/CLAUDE.md +5 -12
  41. package/dist/commands/document-read-cli.js +2 -1
  42. package/dist/commands/document-section-main.js +2 -1
  43. package/dist/commands/read-set-cli.js +194 -134
  44. package/dist/commands/run-prep-main.js +9694 -10173
  45. package/dist/commands/run-step-cli.js +22138 -18285
  46. package/dist/hooks/audit-provision.js +23 -5
  47. package/dist/hooks/chunk-3GKE653K.js +6204 -0
  48. package/dist/hooks/chunk-45CJJOO7.js +1243 -0
  49. package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
  50. package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
  51. package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
  52. package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
  53. package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
  54. package/dist/hooks/chunk-VVNYJWEI.js +30 -0
  55. package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
  56. package/dist/hooks/codex-hook-adapter.js +8 -8
  57. package/dist/hooks/format-edited.js +4 -5
  58. package/dist/hooks/inputs.json +415 -389
  59. package/dist/hooks/pretool-guard.js +7 -6
  60. package/dist/hooks/session-lang.js +101 -258
  61. package/dist/hooks/stop-guard.js +1744 -2576
  62. package/dist/josh.js +7133 -6368
  63. package/dist/version/index.js +161 -1
  64. package/eslint/rules/comment-why-only.js +108 -0
  65. package/package.json +15 -8
  66. package/prompts/coding-standards.md +1 -1
  67. package/prompts/collaboration-workflow/file-edits.md +14 -11
  68. package/prompts/collaboration-workflow/issue-citation.md +0 -4
  69. package/prompts/collaboration-workflow/issue-template.md +40 -21
  70. package/prompts/collaboration-workflow/lane-instructions.md +3 -1
  71. package/prompts/collaboration-workflow/measurement.md +8 -1
  72. package/prompts/collaboration-workflow/operating-rules.md +11 -2
  73. package/prompts/collaboration-workflow/output-bounds.md +6 -2
  74. package/prompts/collaboration-workflow/plan-comment.md +1 -1
  75. package/prompts/collaboration-workflow/principles.md +9 -0
  76. package/prompts/collaboration-workflow/residency.md +3 -2
  77. package/prompts/collaboration-workflow/rule-delivery.md +13 -1
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  79. package/prompts/collaboration-workflow/wip-cap.md +3 -1
  80. package/prompts/review-rubric.md +8 -0
  81. package/prompts/review.md +31 -22
  82. package/prompts/testing-guide.md +1 -0
  83. package/scripts/agent/agent-argv.ts +14 -16
  84. package/scripts/agent/agent-provider.ts +138 -0
  85. package/scripts/agent/agent-role-profile.ts +108 -156
  86. package/scripts/agent/agent-safe-text.ts +20 -5
  87. package/scripts/agent/api-usage-limit.ts +65 -0
  88. package/scripts/agent/claude-agent-argv.ts +19 -9
  89. package/scripts/agent/codex-agent-argv.ts +118 -22
  90. package/scripts/agent/usage-limit-reset.ts +179 -0
  91. package/scripts/auth/claude-token-output.ts +2 -2
  92. package/scripts/auto-ok/auto-ok-author.ts +53 -0
  93. package/scripts/auto-ok/auto-ok-cli.ts +20 -9
  94. package/scripts/backlog/backlog-defect-priority.ts +1 -1
  95. package/scripts/backlog/backlog-drive-cli.ts +6 -1
  96. package/scripts/backlog/backlog-drive-hold.ts +3 -3
  97. package/scripts/backlog/backlog-drive-idle.ts +10 -0
  98. package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
  99. package/scripts/backlog/backlog-drive-launch.ts +8 -2
  100. package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
  101. package/scripts/backlog/backlog-drive-named.ts +28 -7
  102. package/scripts/backlog/backlog-drive-paused.ts +5 -4
  103. package/scripts/backlog/backlog-drive-restore.ts +11 -3
  104. package/scripts/backlog/backlog-drive.ts +21 -60
  105. package/scripts/backlog/backlog-gate-scope.ts +61 -0
  106. package/scripts/backlog/backlog-measure-close.ts +112 -0
  107. package/scripts/backlog/backlog-next.ts +39 -49
  108. package/scripts/backlog/backlog-occupied.ts +2 -2
  109. package/scripts/backlog/backlog-overlap.ts +42 -8
  110. package/scripts/backlog/backlog-plan-cli.ts +4 -1
  111. package/scripts/backlog/backlog-plan-comment.ts +81 -0
  112. package/scripts/backlog/backlog-plan-read.ts +19 -6
  113. package/scripts/backlog/backlog-pool.ts +50 -12
  114. package/scripts/backlog/backlog-rank.ts +60 -13
  115. package/scripts/backlog/backlog-stalled-detect.ts +2 -1
  116. package/scripts/backlog/backlog-waves.ts +17 -2
  117. package/scripts/backlog/backlogrun-launch.ts +7 -4
  118. package/scripts/cost-runtime/context-cut-payback.ts +27 -25
  119. package/scripts/cost-runtime/cost-cli.ts +37 -16
  120. package/scripts/cost-runtime/cost-pricing.ts +9 -0
  121. package/scripts/cost-runtime/cost-verdict.ts +4 -1
  122. package/scripts/delegation/delegation-policy.ts +2 -2
  123. package/scripts/delegation/duplicate-read-guard.ts +1 -0
  124. package/scripts/delegation/investigation-guard.ts +14 -2
  125. package/scripts/delegation/investigation-reads.ts +45 -23
  126. package/scripts/dependabot/dependabot-cooldown.ts +3 -2
  127. package/scripts/discovery/repo-party.ts +80 -20
  128. package/scripts/discovery/session-owner-pin.ts +51 -0
  129. package/scripts/document/capped-print.ts +1 -1
  130. package/scripts/document/command-reference-line-budget.ts +42 -0
  131. package/scripts/document/document-byte-budget.ts +17 -7
  132. package/scripts/document/document-checks.ts +20 -0
  133. package/scripts/document/document-ratchet-check.ts +101 -0
  134. package/scripts/document/document-section.ts +1 -0
  135. package/scripts/document/entry-read-budget.ts +7 -3
  136. package/scripts/document/issue-citation-budget.ts +94 -0
  137. package/scripts/epic/epic-cli-argv.ts +2 -2
  138. package/scripts/epic/epic-fetch.ts +4 -2
  139. package/scripts/epic/epic-graph.ts +7 -2
  140. package/scripts/epic/epic-issue.ts +3 -1
  141. package/scripts/epic/epic-rank.ts +5 -0
  142. package/scripts/exports/unused-members-check.ts +62 -0
  143. package/scripts/exports/unused-members-cli.ts +6 -51
  144. package/scripts/followup/followup-ci-path.ts +67 -0
  145. package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
  146. package/scripts/followup/followup-coderabbit-wait.ts +13 -10
  147. package/scripts/followup/followup-failed-tests.ts +160 -0
  148. package/scripts/followup/followup-job-needs.ts +100 -0
  149. package/scripts/followup/followup-job-rerun.ts +303 -0
  150. package/scripts/followup/followup-main-move.ts +113 -0
  151. package/scripts/followup/git-followup-finish.ts +43 -11
  152. package/scripts/followup/git-followup-pending.ts +3 -3
  153. package/scripts/followup/git-followup-stages.ts +41 -11
  154. package/scripts/followup/git-pr-followup-checks.ts +55 -16
  155. package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
  156. package/scripts/followup/git-pr-followup.ts +62 -27
  157. package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
  158. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
  159. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
  160. package/scripts/gate/busy-average.ts +34 -2
  161. package/scripts/gate/check-related-pinned.ts +43 -0
  162. package/scripts/gate/check-related-reuse.ts +64 -0
  163. package/scripts/gate/check-related-section.ts +57 -0
  164. package/scripts/gate/check-related-unplanned.ts +34 -0
  165. package/scripts/gate/check-related.ts +265 -0
  166. package/scripts/gate/command-memory.ts +1 -1
  167. package/scripts/gate/core-admission.ts +24 -6
  168. package/scripts/gate/core-budget-timing.ts +20 -6
  169. package/scripts/gate/core-budget.ts +40 -11
  170. package/scripts/gate/core-wait-record.ts +3 -2
  171. package/scripts/gate/core-wait.ts +25 -2
  172. package/scripts/gate/core-weights.ts +15 -7
  173. package/scripts/gate/gate-labels.ts +46 -0
  174. package/scripts/gate/gate-ledger.ts +14 -6
  175. package/scripts/gate/gate-plan.ts +103 -58
  176. package/scripts/gate/gate-report.ts +9 -7
  177. package/scripts/gate/gate-run-steps.ts +26 -3
  178. package/scripts/gate/gate-skip.ts +45 -6
  179. package/scripts/gate/gate-static-group.ts +60 -0
  180. package/scripts/gate/gate-steps.ts +2 -1
  181. package/scripts/gate/gate-tree.ts +4 -2
  182. package/scripts/gate/hook-gate-reuse.ts +19 -2
  183. package/scripts/gate/metrics-totals.ts +59 -0
  184. package/scripts/gate/scoped-green.ts +68 -15
  185. package/scripts/gate/since-green.ts +14 -6
  186. package/scripts/gate/structural-checks.ts +96 -0
  187. package/scripts/gate/verification-gate.ts +51 -16
  188. package/scripts/gate/verification-inputs.ts +21 -6
  189. package/scripts/gate/verification-tail.ts +52 -0
  190. package/scripts/gh/gh-author-association.ts +23 -2
  191. package/scripts/gh/git-ai-review-scan.ts +1 -1
  192. package/scripts/gh/git-gh-api-path.ts +12 -3
  193. package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
  194. package/scripts/gh/git-gh-exec.ts +32 -2
  195. package/scripts/gh/git-gh-issue-read.ts +4 -2
  196. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  197. package/scripts/gh/git-gh-issue-write.ts +8 -2
  198. package/scripts/gh/git-gh-issue.ts +12 -5
  199. package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
  200. package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
  201. package/scripts/gh/git-gh-pr-read.ts +74 -13
  202. package/scripts/gh/git-gh-pr-rest.ts +3 -1
  203. package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
  204. package/scripts/gh/git-gh-pr-too-large.ts +20 -0
  205. package/scripts/gh/git-gh-pr.ts +21 -7
  206. package/scripts/gh/git-gh-retry.ts +141 -0
  207. package/scripts/gh/git-pr-ai-review.ts +15 -6
  208. package/scripts/gh/git-pr-checks-eval.ts +3 -0
  209. package/scripts/gh/git-pr-checks.ts +5 -5
  210. package/scripts/git/base-tree.ts +30 -0
  211. package/scripts/git/change-base.ts +4 -10
  212. package/scripts/git/changed-paths-sync.ts +63 -0
  213. package/scripts/git/changed-paths.ts +16 -1
  214. package/scripts/git/git-common-directory.ts +11 -1
  215. package/scripts/git/git-diff-reads.ts +66 -20
  216. package/scripts/git/git-error.ts +9 -2
  217. package/scripts/git/git-schemas.ts +13 -0
  218. package/scripts/git/git-worktree.ts +13 -0
  219. package/scripts/git/gone-branch.ts +4 -1
  220. package/scripts/git/main-merge-budget-conflict.ts +89 -0
  221. package/scripts/git/main-merge-guard.ts +55 -18
  222. package/scripts/git/main-merge-regenerate.ts +59 -25
  223. package/scripts/git/main-merge.ts +53 -5
  224. package/scripts/git/no-base-checkout.ts +127 -0
  225. package/scripts/git/stash/git-stash.ts +11 -2
  226. package/scripts/git/stash/stash-push-cli.ts +10 -0
  227. package/scripts/hooks/batch-guard.ts +40 -21
  228. package/scripts/hooks/edited-cspell.ts +37 -11
  229. package/scripts/hooks/format-edited-file.ts +22 -2
  230. package/scripts/hooks/lane-plan-guard.ts +50 -0
  231. package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
  232. package/scripts/hooks/pre-commit-lint.ts +14 -1
  233. package/scripts/hooks/pre-push-related-switch.ts +10 -0
  234. package/scripts/hooks/pre-push-unit.ts +76 -10
  235. package/scripts/hooks/pretool-guard.ts +2 -0
  236. package/scripts/hooks/reserved-run.ts +14 -1
  237. package/scripts/hooks/run-hook.sh +23 -8
  238. package/scripts/hooks/step-zero-notice.ts +6 -1
  239. package/scripts/hooks/stop-actions.ts +3 -15
  240. package/scripts/hooks/stop-guard.ts +7 -6
  241. package/scripts/init/distributed-paths.ts +1 -0
  242. package/scripts/init/hook-command-rewrite.ts +5 -1
  243. package/scripts/init/hook-launch.ts +4 -2
  244. package/scripts/issue/aggregation-split.ts +52 -0
  245. package/scripts/issue/defect-origin-line.ts +19 -1
  246. package/scripts/issue/defect-rate-cli.ts +5 -102
  247. package/scripts/issue/defect-report.ts +4 -4
  248. package/scripts/issue/effect-audit-cli.ts +178 -0
  249. package/scripts/issue/effect-audit.ts +185 -0
  250. package/scripts/issue/expected-effect-lint.ts +64 -9
  251. package/scripts/issue/git-next-issues.ts +5 -0
  252. package/scripts/issue/issue-body-or-comment.ts +38 -4
  253. package/scripts/issue/issue-cite-cli.ts +1 -3
  254. package/scripts/issue/issue-classification.ts +1 -0
  255. package/scripts/issue/issue-closing-pr.ts +8 -1
  256. package/scripts/issue/issue-evidence-exit.ts +4 -2
  257. package/scripts/issue/issue-file-cap.ts +58 -0
  258. package/scripts/issue/issue-file-cli.ts +111 -38
  259. package/scripts/issue/issue-file-deferral.ts +52 -0
  260. package/scripts/issue/issue-file-fold.ts +29 -10
  261. package/scripts/issue/issue-file-interrupt.ts +85 -0
  262. package/scripts/issue/issue-file-lane-hint.ts +30 -0
  263. package/scripts/issue/issue-file.ts +43 -6
  264. package/scripts/issue/issue-labels.ts +19 -2
  265. package/scripts/issue/issue-lint-cli.ts +27 -2
  266. package/scripts/issue/issue-measured-cli.ts +13 -35
  267. package/scripts/issue/issue-measured-close.ts +58 -0
  268. package/scripts/issue/issue-not-before-label.ts +2 -2
  269. package/scripts/issue/issue-not-before.ts +4 -1
  270. package/scripts/issue/issue-priority-cli.ts +60 -0
  271. package/scripts/issue/issue-priority-raise.ts +55 -0
  272. package/scripts/issue/issue-priority.ts +174 -43
  273. package/scripts/issue/issue-rank.ts +21 -4
  274. package/scripts/issue/issue-read-cli.ts +32 -6
  275. package/scripts/issue/issue-read.ts +37 -12
  276. package/scripts/issue/issue-sample-gate.ts +95 -74
  277. package/scripts/issue/issue-sample-prime.ts +127 -0
  278. package/scripts/issue/issue-sample-required.ts +57 -0
  279. package/scripts/issue/issue-sample-source.ts +12 -11
  280. package/scripts/issue/issue-search.ts +124 -0
  281. package/scripts/issue/issue-state.ts +13 -1
  282. package/scripts/issue/issue-template-section.ts +43 -0
  283. package/scripts/issue/issue-watch-diff.ts +31 -1
  284. package/scripts/issue/issue-watch-hook.ts +11 -2
  285. package/scripts/issue/issue-watch.ts +55 -12
  286. package/scripts/issue/markdown-section.ts +1 -0
  287. package/scripts/issue/measure-declaration.ts +121 -0
  288. package/scripts/issue/measure-rerun-cli.ts +7 -70
  289. package/scripts/issue/plan-consumers-section.ts +22 -1
  290. package/scripts/issue/reproduction-measure.ts +3 -1
  291. package/scripts/issue/size-estimate.ts +90 -0
  292. package/scripts/issue/trusted-measure.ts +84 -0
  293. package/scripts/josh/command-usage.ts +3 -2
  294. package/scripts/josh/durable-state-migrate.ts +105 -0
  295. package/scripts/josh/durable-state.ts +112 -0
  296. package/scripts/josh/josh-aliases.ts +95 -0
  297. package/scripts/josh/josh-command-map.ts +1 -82
  298. package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
  299. package/scripts/josh/josh-commands-ai.ts +5 -5
  300. package/scripts/josh/josh-commands-development.ts +59 -58
  301. package/scripts/josh/josh-commands-issue-write.ts +23 -4
  302. package/scripts/josh/josh-commands-issue.ts +32 -0
  303. package/scripts/josh/josh-commands-lane.ts +87 -4
  304. package/scripts/josh/josh-commands-lint.ts +16 -3
  305. package/scripts/josh/josh-commands-maintenance.ts +15 -0
  306. package/scripts/josh/josh-commands-review.ts +41 -6
  307. package/scripts/josh/josh-commands-ship.ts +56 -8
  308. package/scripts/josh/josh-commands-test-check.ts +80 -0
  309. package/scripts/josh/josh-commands-workflow.ts +1 -1
  310. package/scripts/josh/josh-environment-file.ts +43 -3
  311. package/scripts/josh/josh-in-process.ts +2 -2
  312. package/scripts/josh/josh-logic.ts +5 -3
  313. package/scripts/josh/josh-run.ts +19 -1
  314. package/scripts/josh/run-record-path.ts +15 -0
  315. package/scripts/josh/stamp-file.ts +11 -2
  316. package/scripts/lane/lane-adopt-cli.ts +98 -0
  317. package/scripts/lane/lane-adopt.ts +208 -0
  318. package/scripts/lane/lane-await.ts +5 -8
  319. package/scripts/lane/lane-baseline.ts +123 -0
  320. package/scripts/lane/lane-breakdown-cli.ts +64 -0
  321. package/scripts/lane/lane-breakdown-implement.ts +170 -0
  322. package/scripts/lane/lane-breakdown-laps.ts +107 -0
  323. package/scripts/lane/lane-breakdown-pinned.ts +76 -0
  324. package/scripts/lane/lane-breakdown-text.ts +145 -0
  325. package/scripts/lane/lane-breakdown.ts +239 -0
  326. package/scripts/lane/lane-check-stats.ts +29 -8
  327. package/scripts/lane/lane-child-invocation.ts +9 -2
  328. package/scripts/lane/lane-child-mark-keys.ts +12 -0
  329. package/scripts/lane/lane-child-marker.ts +20 -1
  330. package/scripts/lane/lane-ci-path-stats.ts +73 -0
  331. package/scripts/lane/lane-claim-stats.ts +84 -0
  332. package/scripts/lane/lane-close.ts +8 -3
  333. package/scripts/lane/lane-dispatch-log.ts +3 -3
  334. package/scripts/lane/lane-dispatch.ts +2 -2
  335. package/scripts/lane/lane-gap-stats.ts +7 -6
  336. package/scripts/lane/lane-guard-policy.ts +7 -1
  337. package/scripts/lane/lane-implement-stats.ts +207 -0
  338. package/scripts/lane/lane-implement-subagents.ts +90 -0
  339. package/scripts/lane/lane-investigation-switch.ts +16 -0
  340. package/scripts/lane/lane-ledger-kind.ts +31 -0
  341. package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
  342. package/scripts/lane/lane-ledger-schema.ts +119 -36
  343. package/scripts/lane/lane-ledger-write.ts +257 -0
  344. package/scripts/lane/lane-ledger.ts +31 -177
  345. package/scripts/lane/lane-load.ts +1 -1
  346. package/scripts/lane/lane-measure-cli.ts +176 -59
  347. package/scripts/lane/lane-net-read.ts +44 -0
  348. package/scripts/lane/lane-net-time.ts +212 -0
  349. package/scripts/lane/lane-open.ts +16 -7
  350. package/scripts/lane/lane-output.ts +2 -4
  351. package/scripts/lane/lane-overrun-prompt.ts +147 -0
  352. package/scripts/lane/lane-overrun.ts +274 -0
  353. package/scripts/lane/lane-paths.ts +24 -1
  354. package/scripts/lane/lane-pause-cli.ts +4 -1
  355. package/scripts/lane/lane-paused-spans.ts +97 -0
  356. package/scripts/lane/lane-plan-check.ts +207 -0
  357. package/scripts/lane/lane-plan-cli.ts +41 -0
  358. package/scripts/lane/lane-plan.ts +144 -0
  359. package/scripts/lane/lane-registry.ts +14 -1
  360. package/scripts/lane/lane-relaunch.ts +1 -1
  361. package/scripts/lane/lane-round-one-stats.ts +51 -0
  362. package/scripts/lane/lane-sampler.ts +23 -4
  363. package/scripts/lane/lane-session-action.ts +93 -18
  364. package/scripts/lane/lane-session-broker.ts +49 -21
  365. package/scripts/lane/lane-session-reread-main.ts +14 -0
  366. package/scripts/lane/lane-session-reread.ts +51 -0
  367. package/scripts/lane/lane-ship-lap-stats.ts +74 -0
  368. package/scripts/lane/lane-sleep.ts +58 -0
  369. package/scripts/lane/lane-spent.ts +1 -1
  370. package/scripts/lane/lane-split-stats.ts +28 -0
  371. package/scripts/lane/lane-stage-stats.ts +13 -1
  372. package/scripts/lane/lane-state.ts +41 -9
  373. package/scripts/lane/lane-stats-tables.ts +58 -0
  374. package/scripts/lane/lane-stats.ts +21 -5
  375. package/scripts/lane/lane-stop-stats.ts +51 -19
  376. package/scripts/lane/lane-tail-stats.ts +32 -0
  377. package/scripts/lane/lane-transcript-calls.ts +67 -10
  378. package/scripts/lane/lane-transcript-stats.ts +44 -12
  379. package/scripts/lane/lane-value-cell.ts +72 -0
  380. package/scripts/lane/lane-value.ts +118 -0
  381. package/scripts/lane/lane-withdraw-closed.ts +124 -0
  382. package/scripts/lane/lane-withdraw.ts +108 -31
  383. package/scripts/lane/lane-yield.ts +85 -0
  384. package/scripts/lane/openai-lane-ship.ts +12 -11
  385. package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
  386. package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
  387. package/scripts/lane/openai-lane-supervisor.ts +5 -4
  388. package/scripts/lib/poll.ts +38 -9
  389. package/scripts/lib/settled-together.ts +10 -0
  390. package/scripts/lib/shell-quote.ts +21 -0
  391. package/scripts/lib/token-shape.ts +111 -0
  392. package/scripts/lint/eslint-daemon-sources.ts +79 -0
  393. package/scripts/lint/eslint-daemon.ts +45 -6
  394. package/scripts/lint/eslint-warm-cli.ts +7 -0
  395. package/scripts/lint/eslint-warm.ts +66 -0
  396. package/scripts/lint/import-graph.ts +141 -11
  397. package/scripts/lint/lint-gate-scope.ts +2 -2
  398. package/scripts/lint/lint-parallel.ts +54 -6
  399. package/scripts/lint/lint-related.ts +154 -33
  400. package/scripts/lint/rename-cli.ts +179 -0
  401. package/scripts/lint/rename-identifier.ts +192 -0
  402. package/scripts/lint/type-only-imports.ts +58 -0
  403. package/scripts/notify/telegram-notify.ts +12 -3
  404. package/scripts/observations/observation-record.ts +42 -16
  405. package/scripts/observations/observations-flush.ts +32 -9
  406. package/scripts/overrides/overrides-check.ts +9 -1
  407. package/scripts/release/release-approval-prune.ts +7 -1
  408. package/scripts/repo/required-checks-logic.ts +4 -1
  409. package/scripts/repo/ruleset-check.ts +4 -3
  410. package/scripts/report/markdown-shape.ts +28 -0
  411. package/scripts/report/report-format-reference.ts +15 -1
  412. package/scripts/report/report-labels.ts +12 -1
  413. package/scripts/report/report-lint-child-summary.ts +94 -0
  414. package/scripts/report/report-lint-cli.ts +26 -7
  415. package/scripts/report/report-lint.ts +2 -20
  416. package/scripts/review/prose-stale-cli.ts +88 -0
  417. package/scripts/review/prose-stale-scan.ts +152 -0
  418. package/scripts/review/review-attest.ts +73 -23
  419. package/scripts/review/review-brief-cli.ts +68 -19
  420. package/scripts/review/review-brief-paths.ts +22 -0
  421. package/scripts/review/review-brief.ts +14 -7
  422. package/scripts/review/review-comment-carry.ts +47 -0
  423. package/scripts/review/review-comment-round.ts +85 -22
  424. package/scripts/review/review-finding-ledger.ts +109 -7
  425. package/scripts/review/review-finding-text.ts +43 -0
  426. package/scripts/review/review-interaction.ts +11 -0
  427. package/scripts/review/review-lane-handover.ts +68 -0
  428. package/scripts/review/review-lint-cli.ts +57 -0
  429. package/scripts/review/review-lint-findings.ts +92 -0
  430. package/scripts/review/review-lint-sections.ts +80 -0
  431. package/scripts/review/review-lint.ts +221 -0
  432. package/scripts/review/review-prior-findings.ts +102 -0
  433. package/scripts/review/review-readers-scan.ts +4 -2
  434. package/scripts/review/review-record-cli.ts +177 -54
  435. package/scripts/review/review-record-code-round.ts +55 -0
  436. package/scripts/review/review-record-comment.ts +60 -0
  437. package/scripts/review/review-record-interaction.ts +35 -0
  438. package/scripts/review/review-record-pinned.ts +108 -0
  439. package/scripts/review/review-record.ts +25 -1
  440. package/scripts/review/review-reviewed-tree.ts +307 -0
  441. package/scripts/review/review-round-one-closure.ts +62 -0
  442. package/scripts/review/review-round-reuse.ts +106 -0
  443. package/scripts/review/review-round2-cli.ts +91 -29
  444. package/scripts/review/review-round2-skip-comment.ts +55 -0
  445. package/scripts/review/review-round2.ts +24 -8
  446. package/scripts/review/review-stamps.ts +7 -3
  447. package/scripts/review/review-stop-log.ts +20 -6
  448. package/scripts/review/review-stops-cli.ts +2 -1
  449. package/scripts/review/review-submit-cli.ts +101 -0
  450. package/scripts/rules/agent-model.ts +1 -1
  451. package/scripts/rules/bash-triggers.ts +3 -3
  452. package/scripts/rules/concise-reply.ts +88 -0
  453. package/scripts/rules/credential-body.ts +77 -0
  454. package/scripts/rules/decision-oracle.ts +1 -1
  455. package/scripts/rules/delivered-rules.ts +36 -17
  456. package/scripts/rules/destructive-command.ts +3 -11
  457. package/scripts/rules/direct-issue-comment.ts +34 -12
  458. package/scripts/rules/direct-issue-read.ts +191 -0
  459. package/scripts/rules/direct-pr-create.ts +4 -24
  460. package/scripts/rules/early-heartbeat.ts +2 -0
  461. package/scripts/rules/edit-tool.ts +27 -0
  462. package/scripts/rules/file-edit-rows.ts +17 -0
  463. package/scripts/rules/file-rewrite.ts +146 -0
  464. package/scripts/rules/firing-point.ts +3 -2
  465. package/scripts/rules/gh-api.ts +128 -7
  466. package/scripts/rules/gh-repo-selector.ts +84 -0
  467. package/scripts/rules/git-force.ts +2 -2
  468. package/scripts/rules/guard-remedy.ts +14 -5
  469. package/scripts/rules/implementation-cut.ts +61 -28
  470. package/scripts/rules/investigator-foreground.ts +231 -0
  471. package/scripts/rules/issue-citation.ts +15 -60
  472. package/scripts/rules/issue-comments.ts +9 -4
  473. package/scripts/rules/josh-git-bare.ts +1 -1
  474. package/scripts/rules/lane-background.ts +41 -13
  475. package/scripts/rules/lane-direct-tool.ts +107 -9
  476. package/scripts/rules/lane-interactive-ask.ts +4 -1
  477. package/scripts/rules/lane-park.ts +4 -2
  478. package/scripts/rules/lane-spilled-read.ts +90 -0
  479. package/scripts/rules/lane-split-park.ts +1 -1
  480. package/scripts/rules/lane-switch-main.ts +4 -4
  481. package/scripts/rules/last-prompt.ts +20 -47
  482. package/scripts/rules/oracle-consulted.ts +1 -1
  483. package/scripts/rules/permission-guards.ts +5 -1
  484. package/scripts/rules/piped-verification-shapes.ts +110 -0
  485. package/scripts/rules/piped-verification.ts +67 -17
  486. package/scripts/rules/poll-loop.ts +72 -9
  487. package/scripts/rules/pr-merge.ts +5 -5
  488. package/scripts/rules/pre-gate-cut.ts +3 -3
  489. package/scripts/rules/protected-files.ts +88 -24
  490. package/scripts/rules/reply-language.ts +3 -17
  491. package/scripts/rules/reply-prose.ts +34 -0
  492. package/scripts/rules/repo-admin.ts +132 -0
  493. package/scripts/rules/reviewer-foreground.ts +88 -0
  494. package/scripts/rules/round-one-check-related.ts +120 -0
  495. package/scripts/rules/rule-body-guard.ts +5 -15
  496. package/scripts/rules/rule-value-cache.ts +53 -5
  497. package/scripts/rules/rule-value-candidates.ts +84 -0
  498. package/scripts/rules/rule-value-cli.ts +59 -11
  499. package/scripts/rules/rule-value.ts +17 -4
  500. package/scripts/rules/run-tail-rule.ts +1 -1
  501. package/scripts/rules/sed-in-place.ts +196 -0
  502. package/scripts/rules/shell-segments.ts +10 -2
  503. package/scripts/rules/stop-rule-rows.ts +1 -13
  504. package/scripts/rules/stop-rules.ts +33 -58
  505. package/scripts/rules/test-declared-commit.ts +1 -1
  506. package/scripts/rules/third-party-write.ts +110 -23
  507. package/scripts/rules/timed-rerun.ts +1 -1
  508. package/scripts/rules/turn-filings.ts +90 -0
  509. package/scripts/rules/worktree-guard.ts +22 -7
  510. package/scripts/run/board/run-board-ending-reason.ts +6 -2
  511. package/scripts/run/board/run-board-fit.ts +24 -1
  512. package/scripts/run/board/run-board-found.ts +134 -0
  513. package/scripts/run/board/run-board-header.ts +8 -0
  514. package/scripts/run/board/run-board-labels.ts +11 -1
  515. package/scripts/run/board/run-board-lane-time.ts +1 -0
  516. package/scripts/run/board/run-board-link.ts +15 -2
  517. package/scripts/run/board/run-board-notes.ts +7 -1
  518. package/scripts/run/board/run-board-operation.ts +4 -2
  519. package/scripts/run/board/run-board-phase.ts +2 -5
  520. package/scripts/run/board/run-board-plan.ts +1 -1
  521. package/scripts/run/board/run-board-read.ts +46 -1
  522. package/scripts/run/board/run-board-reason.ts +20 -8
  523. package/scripts/run/board/run-board-render-checks.ts +3 -2
  524. package/scripts/run/board/run-board-render-drawn.ts +11 -0
  525. package/scripts/run/board/run-board-render-found.ts +157 -0
  526. package/scripts/run/board/run-board-render-lead.ts +9 -0
  527. package/scripts/run/board/run-board-render-legend.ts +19 -0
  528. package/scripts/run/board/run-board-render-tail.ts +1 -1
  529. package/scripts/run/board/run-board-render.ts +82 -18
  530. package/scripts/run/board/run-board-spin.ts +1 -3
  531. package/scripts/run/board/run-board-status.ts +2 -0
  532. package/scripts/run/board/run-board-tick.ts +39 -10
  533. package/scripts/run/carry/run-carry-ended.ts +3 -2
  534. package/scripts/run/carry/run-carry.ts +3 -2
  535. package/scripts/run/cut/run-cut-cli.ts +33 -133
  536. package/scripts/run/cut/run-cut-digest.ts +152 -0
  537. package/scripts/run/cut/run-cut-handoff.ts +52 -8
  538. package/scripts/run/cut/run-cut-report.ts +24 -3
  539. package/scripts/run/cut/run-cut-resume.ts +129 -0
  540. package/scripts/run/cut/run-cut-review.ts +105 -0
  541. package/scripts/run/cut/run-cut.ts +18 -7
  542. package/scripts/run/detached-launch.ts +1 -1
  543. package/scripts/run/entry/run-entry-cli.ts +88 -19
  544. package/scripts/run/entry/run-entry-spill.ts +83 -0
  545. package/scripts/run/entry/run-entry-stop.ts +5 -1
  546. package/scripts/run/entry/run-entry-trust.ts +91 -0
  547. package/scripts/run/event/run-event-baseline.ts +56 -0
  548. package/scripts/run/event/run-event-filed.ts +9 -3
  549. package/scripts/run/event/run-event-overrun.ts +197 -0
  550. package/scripts/run/event/run-event-render.ts +4 -0
  551. package/scripts/run/event/run-event-stream.ts +25 -3
  552. package/scripts/run/hold/run-stop-notify.ts +3 -8
  553. package/scripts/run/merge/run-merge-cap.ts +57 -0
  554. package/scripts/run/merge/run-merge-cli.ts +66 -23
  555. package/scripts/run/merge/run-merge-outcome.ts +2 -0
  556. package/scripts/run/merge/run-merge-ship.ts +13 -3
  557. package/scripts/run/merge/run-merge-signals.ts +40 -23
  558. package/scripts/run/merge/run-merge-steps.ts +56 -36
  559. package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
  560. package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
  561. package/scripts/run/merge/run-merge.ts +41 -14
  562. package/scripts/run/review/run-review-cli.ts +306 -20
  563. package/scripts/run/review/run-review-early-push.ts +121 -0
  564. package/scripts/run/review/run-review-join-read.ts +94 -0
  565. package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
  566. package/scripts/run/review/run-review-pinned-join.ts +216 -0
  567. package/scripts/run/review/run-review-pinned-steps.ts +383 -0
  568. package/scripts/run/review/run-review-pinned-tree.ts +37 -0
  569. package/scripts/run/review/run-review-pinned.ts +53 -0
  570. package/scripts/run/review/run-review-steps.ts +70 -34
  571. package/scripts/run/review/run-review.ts +26 -6
  572. package/scripts/run/run-parent-cut-hook.ts +3 -4
  573. package/scripts/run/run-prep.ts +1 -0
  574. package/scripts/run/run-stage-read.ts +2 -6
  575. package/scripts/run/run-step-cli.ts +17 -5
  576. package/scripts/run/run-step-lane-plan.ts +27 -0
  577. package/scripts/run/run-step-section.ts +26 -7
  578. package/scripts/run/run-step.ts +5 -2
  579. package/scripts/run/ship/run-ship-args.ts +19 -0
  580. package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
  581. package/scripts/run/ship/run-ship-cli.ts +131 -39
  582. package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
  583. package/scripts/run/ship/run-ship-detach.ts +10 -4
  584. package/scripts/run/ship/run-ship-handover.ts +106 -0
  585. package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
  586. package/scripts/run/ship/run-ship-lap.ts +63 -0
  587. package/scripts/run/ship/run-ship-next.ts +27 -26
  588. package/scripts/run/ship/run-ship-overlap.ts +8 -3
  589. package/scripts/run/ship/run-ship-place.ts +9 -2
  590. package/scripts/run/ship/run-ship-planned.ts +92 -0
  591. package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
  592. package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
  593. package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
  594. package/scripts/run/ship/run-ship-preflight.ts +16 -4
  595. package/scripts/run/ship/run-ship-probe.ts +23 -9
  596. package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
  597. package/scripts/run/ship/run-ship-relaunch.ts +51 -0
  598. package/scripts/run/ship/run-ship-return.ts +103 -29
  599. package/scripts/run/ship/run-ship-review-reason.ts +24 -0
  600. package/scripts/run/ship/run-ship-review-steps.ts +230 -59
  601. package/scripts/run/ship/run-ship-review.ts +171 -51
  602. package/scripts/run/ship/run-ship-reviewer.ts +138 -42
  603. package/scripts/run/ship/run-ship-scoped.ts +39 -10
  604. package/scripts/run/ship/run-ship-stage.ts +49 -13
  605. package/scripts/run/ship/run-ship-steps.ts +24 -4
  606. package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
  607. package/scripts/run/ship/run-ship-stop-context.ts +50 -13
  608. package/scripts/run/ship/run-ship-sync.ts +171 -30
  609. package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
  610. package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
  611. package/scripts/run/ship/run-ship-waiter.ts +114 -0
  612. package/scripts/run/ship/run-ship.ts +16 -2
  613. package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
  614. package/scripts/run/wake/run-wake-cli.ts +6 -113
  615. package/scripts/run/wake/run-wake-code.ts +53 -11
  616. package/scripts/run/wake/run-wake-driver.ts +5 -3
  617. package/scripts/run/wake/run-wake-finish.ts +137 -0
  618. package/scripts/run/wake/run-wake-loop.ts +5 -3
  619. package/scripts/run/wake/run-wake-ports.ts +11 -3
  620. package/scripts/run/wake/run-wake-session.ts +2 -0
  621. package/scripts/run/wake/run-wake.ts +3 -2
  622. package/scripts/safe-chain/ci-installer-pin.ts +49 -8
  623. package/scripts/safe-chain/project-config.ts +3 -2
  624. package/scripts/security/secretlint-scan.ts +42 -9
  625. package/scripts/security/security-audit-provision-main.ts +9 -1
  626. package/scripts/security/security-audit-provision.ts +1 -1
  627. package/scripts/security/security-audit.ts +21 -1
  628. package/scripts/security/sonar-file.ts +10 -5
  629. package/scripts/split/split-assess.ts +12 -3
  630. package/scripts/split/split-early-notice.ts +116 -0
  631. package/scripts/split/split-measure.ts +20 -2
  632. package/scripts/split/split-resolution.ts +107 -0
  633. package/scripts/test/direct-importers.ts +185 -0
  634. package/scripts/test/pure-files-entries.ts +83 -0
  635. package/scripts/test/related-heavy-tests.ts +81 -0
  636. package/scripts/test/test-no-base.ts +88 -0
  637. package/scripts/test/test-pinned-cache-logic.ts +363 -0
  638. package/scripts/test/test-pinned-cache.ts +337 -0
  639. package/scripts/test/test-pinned-emit.ts +49 -0
  640. package/scripts/test/test-pinned-functions.ts +116 -0
  641. package/scripts/test/test-pinned-ledger.ts +80 -0
  642. package/scripts/test/test-pinned-logic.ts +208 -20
  643. package/scripts/test/test-pinned-main.ts +84 -18
  644. package/scripts/test/test-pinned-pool.ts +73 -0
  645. package/scripts/test/test-pinned-probe.ts +254 -0
  646. package/scripts/test/test-pinned-prune-logic.ts +118 -0
  647. package/scripts/test/test-pinned-reach.ts +194 -0
  648. package/scripts/test/test-pinned-record.ts +196 -0
  649. package/scripts/test/test-pinned-rewire.ts +148 -0
  650. package/scripts/test/test-pinned-spawn.ts +81 -0
  651. package/scripts/test/test-pinned-stage.ts +70 -0
  652. package/scripts/test/test-pinned.ts +258 -47
  653. package/scripts/test/test-reach-cli.ts +61 -0
  654. package/scripts/test/test-read-map.ts +35 -11
  655. package/scripts/test/test-read-recording.ts +17 -5
  656. package/scripts/test/test-read-trace.ts +26 -8
  657. package/scripts/test/test-red-logic.ts +46 -7
  658. package/scripts/test/test-red.ts +34 -14
  659. package/scripts/test/test-related-claim.ts +37 -0
  660. package/scripts/test/test-related-main.ts +4 -1
  661. package/scripts/test/test-related-moved.ts +91 -0
  662. package/scripts/test/test-related-record.ts +43 -23
  663. package/scripts/test/test-related-scope.ts +99 -34
  664. package/scripts/test/test-related-synced.ts +135 -0
  665. package/scripts/test/test-related-touched.ts +84 -0
  666. package/scripts/test/test-related.ts +167 -83
  667. package/scripts/test/test-unit-guard.ts +28 -5
  668. package/scripts/test/unit-durations.ts +41 -0
  669. package/scripts/test/unit-worker-share.ts +5 -4
  670. package/scripts/test/vitest-cache-directory.ts +19 -0
  671. package/scripts/test/vitest-cli-excludes.ts +14 -0
  672. package/scripts/test/vitest-failure-summary.ts +111 -0
  673. package/scripts/test/whole-suite-files.ts +12 -1
  674. package/scripts/time-runtime/time-batch-guard.ts +42 -4
  675. package/scripts/time-runtime/time-bundle-call.ts +1 -1
  676. package/scripts/time-runtime/time-bundles.ts +36 -5
  677. package/scripts/time-runtime/time-markers.ts +2 -2
  678. package/scripts/time-runtime/time-search-call.ts +38 -0
  679. package/scripts/time-runtime/time-shell.ts +1 -1
  680. package/scripts/time-runtime/time-transcript-line.ts +17 -5
  681. package/scripts/version/development-engines-verdict.ts +124 -0
  682. package/scripts/version/latest-corepack.ts +2 -0
  683. package/templates/gitignore +2 -0
  684. package/templates/workflows/ci.yml +41 -0
  685. package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
  686. package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
  687. package/dist/hooks/chunk-TUIFS5E7.js +0 -72
  688. package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
  689. package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
  690. package/scripts/rules/filing-cap.ts +0 -139
@@ -0,0 +1,43 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { package_path } from '#scripts/init/init-paths'
3
+ import { markdown_section } from './markdown-section'
4
+
5
+ // One section of the Issue body template, cut from the fenced template in `issue-template.md` — its
6
+ // single source — so a refusal for a missing heading can print the section to paste back in rather than
7
+ // sending the filer to reopen the document (joshuafolkken/kit#5206).
8
+ const TEMPLATE_PATH = 'prompts/collaboration-workflow/issue-template.md'
9
+ const TEMPLATE_FENCE = '````'
10
+
11
+ // The lines inside the document's four-backtick template fence; an empty array when it has none.
12
+ function template_lines(document: string): ReadonlyArray<string> {
13
+ const lines = markdown_section.lines_of(document)
14
+ const start = lines.findIndex((line) => line.startsWith(TEMPLATE_FENCE))
15
+ const end = lines.findIndex((line, index) => index > start && line.trim() === TEMPLATE_FENCE)
16
+
17
+ if (start === -1 || end === -1) return []
18
+
19
+ return lines.slice(start + 1, end)
20
+ }
21
+
22
+ // The heading and its template lines, trailing blank lines dropped; `undefined` when the template has no
23
+ // such section.
24
+ function section_of(document: string, heading: string): string | undefined {
25
+ const template = template_lines(document).join('\n')
26
+
27
+ if (!markdown_section.has_line(template, heading)) return undefined
28
+
29
+ return [heading, ...markdown_section.section_lines(template, heading)].join('\n').trimEnd()
30
+ }
31
+
32
+ // A package without the template prints the bare problem rather than failing the lint.
33
+ function section(heading: string): string | undefined {
34
+ const template_path = package_path(TEMPLATE_PATH)
35
+
36
+ if (!existsSync(template_path)) return undefined
37
+
38
+ return section_of(readFileSync(template_path, 'utf8'), heading)
39
+ }
40
+
41
+ const issue_template_section = { section, section_of }
42
+
43
+ export { issue_template_section }
@@ -21,6 +21,12 @@ import { z } from 'zod'
21
21
  // body withheld, costing one `josh issue:ack` rather than a missed correction, and its marker is not
22
22
  // trusted.
23
23
  //
24
+ // **A body edit is a person's update only when the issue's author has write access.** GitHub does not
25
+ // say who edited a body, and its author can always edit it, so a body under an author without write
26
+ // access is never handed to a run as an update: its edit becomes a notice with the text withheld,
27
+ // which asks for nothing and holds no merge. A `MEMBER` / `COLLABORATOR` author whose lookup failed
28
+ // is treated like their comment: the edit is announced with its text withheld and stays pending.
29
+ //
24
30
  // **Comment edits are out of scope.** A new comment carries an id above the baseline's; an edited
25
31
  // one keeps its id and is not seen. The body is compared whole, so its edits are.
26
32
 
@@ -33,6 +39,8 @@ const UNKNOWN_TIME = '(undated)'
33
39
  const UNVERIFIED_BODY =
34
40
  "(body withheld — the author's write access could not be checked; read it with `josh issue:read`)"
35
41
 
42
+ const WITHHELD_BODY_EDIT = `The body of this run's issue was edited, but its author is not ${gh_author_association.TRUSTED_LIST}, so the edit is withheld: it is not an instruction to this run and holds nothing — carry on.`
43
+
36
44
  const BODY_HEADING = 'Body edited:'
37
45
  const REMOVED_PREFIX = '- '
38
46
  const ADDED_PREFIX = '+ '
@@ -62,6 +70,8 @@ interface WatchComment {
62
70
 
63
71
  interface WatchSnapshot {
64
72
  body: string
73
+ // Whether the issue's author has write access — `undefined` when the lookup failed.
74
+ is_body_trusted: boolean | undefined
65
75
  comments: ReadonlyArray<WatchComment>
66
76
  }
67
77
 
@@ -171,9 +181,20 @@ function new_person_comments(
171
181
  )
172
182
  }
173
183
 
184
+ function is_body_edited(baseline: WatchBaseline, snapshot: WatchSnapshot): boolean {
185
+ return to_lf(baseline.body) !== to_lf(snapshot.body)
186
+ }
187
+
188
+ function unread_body(baseline: WatchBaseline, snapshot: WatchSnapshot): string | undefined {
189
+ if (snapshot.is_body_trusted === true) return body_section(baseline.body, snapshot.body)
190
+ if (snapshot.is_body_trusted === false || !is_body_edited(baseline, snapshot)) return undefined
191
+
192
+ return `${BODY_HEADING}${LINE_SEPARATOR}${UNVERIFIED_BODY}`
193
+ }
194
+
174
195
  // The unread update as the text a run is shown, or `undefined` when a person changed nothing.
175
196
  function unread_diff(baseline: WatchBaseline, snapshot: WatchSnapshot): string | undefined {
176
- const body = body_section(baseline.body, snapshot.body)
197
+ const body = unread_body(baseline, snapshot)
177
198
  const comments = new_person_comments(baseline, snapshot).map((comment) =>
178
199
  comment_section(comment),
179
200
  )
@@ -182,11 +203,20 @@ function unread_diff(baseline: WatchBaseline, snapshot: WatchSnapshot): string |
182
203
  return sections.length === 0 ? undefined : sections.join(SECTION_SEPARATOR)
183
204
  }
184
205
 
206
+ // The notice for a body edit `unread_diff` withholds, or `undefined` when there is none.
207
+ function withheld_notice(baseline: WatchBaseline, snapshot: WatchSnapshot): string | undefined {
208
+ if (snapshot.is_body_trusted !== false || !is_body_edited(baseline, snapshot)) return undefined
209
+
210
+ return WITHHELD_BODY_EDIT
211
+ }
212
+
185
213
  const issue_watch_diff = {
186
214
  RUN_MARKER,
215
+ WITHHELD_BODY_EDIT,
187
216
  baseline_of,
188
217
  parse_comments,
189
218
  unread_diff,
219
+ withheld_notice,
190
220
  }
191
221
 
192
222
  export type { WatchBaseline, WatchComment, WatchSnapshot }
@@ -22,7 +22,16 @@ const POLL_INTERVAL_MS = 180_000
22
22
  const POLL_TIMEOUT_MS = 5000
23
23
 
24
24
  function is_new_to_run(record: WatchRecord | undefined): record is WatchRecord {
25
- return record?.pending !== undefined && record.pending !== record.shown
25
+ const news = record === undefined ? undefined : issue_watch.news_of(record)
26
+
27
+ return news !== undefined && news !== record?.shown
28
+ }
29
+
30
+ // A withheld body edit is shown as its own notice: it asks for no `josh issue:ack`.
31
+ function message_of(record: WatchRecord): string {
32
+ return record.pending === undefined
33
+ ? (record.notice ?? '')
34
+ : issue_watch_gate.unread_message(record)
26
35
  }
27
36
 
28
37
  async function new_update_reason(now: Date): Promise<string | undefined> {
@@ -36,7 +45,7 @@ async function new_update_reason(now: Date): Promise<string | undefined> {
36
45
 
37
46
  issue_watch.mark_shown(record)
38
47
 
39
- return issue_watch_gate.unread_message(record)
48
+ return message_of(record)
40
49
  }
41
50
 
42
51
  async function watch_reason(now: Date = new Date()): Promise<string | undefined> {
@@ -1,3 +1,4 @@
1
+ import { gh_author_association } from '#scripts/gh/gh-author-association'
1
2
  import { git_gh_api_path } from '#scripts/gh/git-gh-api-path'
2
3
  import { git_gh_conditional } from '#scripts/gh/git-gh-conditional'
3
4
  import { git_gh_issue_read } from '#scripts/gh/git-gh-issue-read'
@@ -30,10 +31,18 @@ const record_schema = z.object({
30
31
  checked_at: z.string(),
31
32
  etag: z.string().optional(),
32
33
  pending: z.string().optional(),
34
+ // A body edit withheld from the run (`issue_watch_diff.withheld_notice`) — shown, never a hold.
35
+ notice: z.string().optional(),
33
36
  shown: z.string().optional(),
34
37
  })
35
38
 
36
- const issue_schema = z.looseObject({ body: z.string().nullish(), state: z.string() })
39
+ const issue_schema = z.looseObject({
40
+ body: z.string().nullish(),
41
+ state: z.string(),
42
+ ...gh_author_association.author_fields_schema,
43
+ })
44
+
45
+ type IssueRead = z.infer<typeof issue_schema>
37
46
 
38
47
  type WatchRecord = z.infer<typeof record_schema>
39
48
 
@@ -71,17 +80,28 @@ interface FetchRequest {
71
80
  timeout_ms?: number | undefined
72
81
  }
73
82
 
74
- async function read_snapshot(
83
+ async function read_comments(
75
84
  request: FetchRequest,
76
- body: string,
77
- etag: string | undefined,
78
- ): Promise<Fetched> {
79
- const comments = await issue_watch_diff.parse_comments(
85
+ ): Promise<WatchSnapshot['comments'] | undefined> {
86
+ return await issue_watch_diff.parse_comments(
80
87
  await git_gh_issue_read.issue_list_comments(request.issue, request.timeout_ms),
81
88
  request.timeout_ms,
82
89
  )
90
+ }
91
+
92
+ async function read_snapshot(
93
+ request: FetchRequest,
94
+ issue: IssueRead,
95
+ etag: string | undefined,
96
+ ): Promise<Fetched> {
97
+ const [comments, is_body_trusted] = await Promise.all([
98
+ read_comments(request),
99
+ gh_author_association.trust_of(gh_author_association.author_of(issue, request.timeout_ms)),
100
+ ])
83
101
 
84
- return comments === undefined ? UNREADABLE : { kind: 'read', snapshot: { body, comments }, etag }
102
+ if (comments === undefined) return UNREADABLE
103
+
104
+ return { kind: 'read', snapshot: { body: issue.body ?? '', is_body_trusted, comments }, etag }
85
105
  }
86
106
 
87
107
  async function fetch_issue(request: FetchRequest): Promise<Fetched> {
@@ -98,7 +118,7 @@ async function fetch_issue(request: FetchRequest): Promise<Fetched> {
98
118
  if (fields === undefined) return UNREADABLE
99
119
  if (fields.state === CLOSED_STATE) return { kind: 'closed' }
100
120
 
101
- return await read_snapshot(request, fields.body ?? '', read.etag)
121
+ return await read_snapshot(request, fields, read.etag)
102
122
  }
103
123
 
104
124
  // `acknowledged_at` is when a person's update was last taken in — the baseline's own read, or an
@@ -115,6 +135,20 @@ function fresh_record(
115
135
  return { issue, ...baseline, acknowledged_at, checked_at, etag: fetched.etag }
116
136
  }
117
137
 
138
+ // The previous `acknowledged_at` when a re-read of the same issue takes nothing in, so a `josh
139
+ // run:entry` re-run keeps a review briefed before it valid; `undefined` (now) otherwise.
140
+ function kept_acknowledgement(
141
+ previous: WatchRecord | undefined,
142
+ issue: string,
143
+ fetched: Extract<Fetched, { kind: 'read' }>,
144
+ ): string | undefined {
145
+ if (previous?.issue !== issue) return undefined
146
+
147
+ const diff = issue_watch_diff.unread_diff(previous, fetched.snapshot)
148
+
149
+ return diff === undefined ? previous.acknowledged_at : undefined
150
+ }
151
+
118
152
  // Called by `josh run:entry` once it has printed the issue. `false` when the issue could not be read,
119
153
  // which leaves no record — a watch with no baseline would call the whole issue unread.
120
154
  async function record_baseline(
@@ -126,7 +160,9 @@ async function record_baseline(
126
160
 
127
161
  if (fetched.kind !== 'read') return false
128
162
 
129
- write_record(fresh_record(issue, fetched, now), root)
163
+ const acknowledged_at = kept_acknowledgement(read_record(root), issue, fetched)
164
+
165
+ write_record(fresh_record(issue, fetched, now, acknowledged_at), root)
130
166
 
131
167
  return true
132
168
  }
@@ -145,8 +181,9 @@ function with_fetched(record: WatchRecord, fetched: Fetched, checked_at: string)
145
181
  if (fetched.kind !== 'read') return { ...record, checked_at }
146
182
 
147
183
  const pending = issue_watch_diff.unread_diff(record, fetched.snapshot)
184
+ const notice = issue_watch_diff.withheld_notice(record, fetched.snapshot)
148
185
 
149
- return { ...record, checked_at, etag: fetched.etag, pending }
186
+ return { ...record, checked_at, etag: fetched.etag, pending, notice }
150
187
  }
151
188
 
152
189
  interface PollOptions {
@@ -191,9 +228,14 @@ async function poll(options: PollOptions, root?: string): Promise<WatchRecord |
191
228
  return await refresh(record, options, now, root)
192
229
  }
193
230
 
194
- // Marks a pending update shown, so the hook stops once per update rather than on every call.
231
+ // What the hook shows: a pending update, else a withheld edit's notice.
232
+ function news_of(record: WatchRecord): string | undefined {
233
+ return record.pending ?? record.notice
234
+ }
235
+
236
+ // Marks the news shown, so the hook stops once per update rather than on every call.
195
237
  function mark_shown(record: WatchRecord, root?: string): WatchRecord {
196
- return write_record({ ...record, shown: record.pending }, root)
238
+ return write_record({ ...record, shown: news_of(record) }, root)
197
239
  }
198
240
 
199
241
  interface Acknowledged {
@@ -230,6 +272,7 @@ const issue_watch = {
230
272
  clear,
231
273
  clear_here,
232
274
  mark_shown,
275
+ news_of,
233
276
  poll,
234
277
  read_record,
235
278
  record_baseline,
@@ -94,6 +94,7 @@ const markdown_section = {
94
94
  section_lines,
95
95
  section_lines_matching,
96
96
  fence_free_lines,
97
+ lines_of,
97
98
  unfenced_lines,
98
99
  has_line,
99
100
  has_unfenced_line,
@@ -0,0 +1,121 @@
1
+ import { z } from 'zod'
2
+ import { markdown_section } from './markdown-section'
3
+
4
+ // joshuafolkken/kit#4621: a follow-up measurement ("measure K merges after #N") took a lane for 20–40
5
+ // minutes to run one command and compare its value with a bound — work `backlog:drive` can do itself.
6
+ // The body declares that command and the bound once, under the background heading, so the drive runs
7
+ // it when the issue comes due and closes it on a pass; only a miss or an unreadable value takes a lane.
8
+ //
9
+ // **The value is the command's last non-empty output line, read as one number.** Anything else — a
10
+ // unit, a sentence, a failure — is unreadable, so a person never relies on how a free-text line parses.
11
+
12
+ const BACKGROUND_HEADING = '## 背景'
13
+ const PREFIX = '- 計測宣言:'
14
+ // joshuafolkken/kit#5144: a follow-up whose value no one command prints (an Actions record, an external
15
+ // service) says why under the same heading instead, so it files without a declaration.
16
+ const ABSENT_PREFIX = '- 計測宣言なし:'
17
+ const ABSENT = `${ABSENT_PREFIX} <理由>`
18
+ const DECLARATION = `${PREFIX} \`<command>\` <=|<|>=|> <number>`
19
+ const VALUE_PATTERN =
20
+ /^`(?<command>[^`\s][^`]*)`\s*(?<operator><=|>=|<|>)\s*(?<threshold>-?\d+(?:\.\d+)?)$/u
21
+ const NUMBER_PATTERN = /^-?\d+(?:\.\d+)?$/u
22
+ // The refusal ends in a line ready to paste (joshuafolkken/kit#5225).
23
+ const EXAMPLE = `${PREFIX} \`pnpm josh lane:net 5168\` <= 40`
24
+ const MALFORMED = `\`${PREFIX}\` must be written ${DECLARATION}\n${EXAMPLE}`
25
+ const declaration_schema = z.object({
26
+ command: z.string().trim(),
27
+ operator: z.enum(['<=', '<', '>=', '>']),
28
+ threshold: z.coerce.number(),
29
+ })
30
+
31
+ type MeasureDeclaration = z.infer<typeof declaration_schema>
32
+ type Operator = MeasureDeclaration['operator']
33
+ type MeasureVerdict = { kind: 'pass' | 'fail'; value: number } | { kind: 'unreadable' }
34
+
35
+ const COMPARE: Readonly<Record<Operator, (value: number, threshold: number) => boolean>> = {
36
+ '<=': (value, threshold) => value <= threshold,
37
+ '<': (value, threshold) => value < threshold,
38
+ '>=': (value, threshold) => value >= threshold,
39
+ '>': (value, threshold) => value > threshold,
40
+ }
41
+
42
+ // The value after `prefix` under the background heading, or `undefined` when the body has no such line.
43
+ function background_value(body: string, prefix: string): string | undefined {
44
+ const background = markdown_section.section_lines(body, BACKGROUND_HEADING).join('\n')
45
+ const line = markdown_section
46
+ .unfenced_lines(background)
47
+ .map((text) => text.trim())
48
+ .find((text) => text.startsWith(prefix))
49
+
50
+ return line?.slice(prefix.length).trim()
51
+ }
52
+
53
+ function declared_value(body: string): string | undefined {
54
+ return background_value(body, PREFIX)
55
+ }
56
+
57
+ // Whether the body writes a declaration line — a malformed one is `problems`' refusal — or says why it
58
+ // has none; an empty reason says nothing.
59
+ function is_stated(body: string): boolean {
60
+ if (declared_value(body) !== undefined) return true
61
+
62
+ return (background_value(body, ABSENT_PREFIX) ?? '').length > 0
63
+ }
64
+
65
+ function parsed(value: string): MeasureDeclaration | undefined {
66
+ const read = declaration_schema.safeParse(VALUE_PATTERN.exec(value)?.groups)
67
+
68
+ return read.success ? read.data : undefined
69
+ }
70
+
71
+ // The declaration, or `undefined` when there is none or it does not parse — `issue:file` refuses a
72
+ // malformed one, and the drive hands an issue it cannot read to a lane as before.
73
+ function parse(body: string): MeasureDeclaration | undefined {
74
+ const value = declared_value(body)
75
+
76
+ return value === undefined ? undefined : parsed(value)
77
+ }
78
+
79
+ function problems(body: string): ReadonlyArray<string> {
80
+ const value = declared_value(body)
81
+
82
+ return value === undefined || parsed(value) !== undefined ? [] : [MALFORMED]
83
+ }
84
+
85
+ function value_of(output: string): number | undefined {
86
+ const last = output
87
+ .split('\n')
88
+ .map((line) => line.trim())
89
+ .findLast((line) => line.length > 0)
90
+
91
+ return last !== undefined && NUMBER_PATTERN.test(last) ? Number(last) : undefined
92
+ }
93
+
94
+ function judge(declaration: MeasureDeclaration, output: string): MeasureVerdict {
95
+ const value = value_of(output)
96
+
97
+ if (value === undefined) return { kind: 'unreadable' }
98
+
99
+ const is_pass = COMPARE[declaration.operator](value, declaration.threshold)
100
+
101
+ return { kind: is_pass ? 'pass' : 'fail', value }
102
+ }
103
+
104
+ function format(declaration: MeasureDeclaration): string {
105
+ return `\`${declaration.command}\` ${declaration.operator} ${String(declaration.threshold)}`
106
+ }
107
+
108
+ const measure_declaration = {
109
+ ABSENT,
110
+ DECLARATION,
111
+ EXAMPLE,
112
+ PREFIX,
113
+ format,
114
+ is_stated,
115
+ judge,
116
+ parse,
117
+ problems,
118
+ }
119
+
120
+ export type { MeasureDeclaration, MeasureVerdict }
121
+ export { measure_declaration }
@@ -1,65 +1,33 @@
1
1
  #!/usr/bin/env tsx
2
- import { execSync } from 'node:child_process'
3
2
  import { fileURLToPath } from 'node:url'
4
- import { gh_author_association } from '#scripts/gh/gh-author-association'
5
- import { git_gh_issue_read } from '#scripts/gh/git-gh-issue-read'
6
- import { parse_json_object_or_undefined } from '#scripts/git/parse-json-array'
7
3
  import { issue_number_shape } from '#scripts/issue/issue-number-shape'
8
- import { error_text } from '#scripts/lib/error-message'
9
4
  import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
10
- import { z } from 'zod'
11
5
  import { baseline_measure, type Baseline } from './baseline-measure'
12
- import { session_cite } from './session-cite'
6
+ import { trusted_measure } from './trusted-measure'
13
7
 
14
8
  // `josh measure:rerun <N>` — read a behavior-change Issue body after merge, re-run each baseline
15
9
  // command, and print the before/after pair. A value that did not move means
16
10
  // the premise the rule rested on is refuted, so a line is appended to the observation ledger — reusing
17
11
  // that append-only, same-key mechanism rather than a second one.
18
12
  //
19
- // **The baseline is shell, and the body is written by whoever filed the Issue**.
20
- // A baseline needs pipes and loops (`git log … | wc -l`, `for d in $(ls …)`), so an allow-list of
21
- // commands cannot make it safe; what can is who wrote it. The input is therefore the Issue number, not
22
- // a file, so the author's `author_association` arrives with the body from the same REST read, and only
23
- // a body written by someone with write access to the repository is run.
13
+ // The baseline is shell, so only a body whose author can write to the repository is run
14
+ // (`trusted-measure.ts`).
24
15
 
25
16
  const FAILURE_EXIT_CODE = 1
26
17
  const ARGV_OFFSET = 2
27
- const ISSUE_FIELDS = 'body,author_association,user'
28
- const UNKNOWN_ASSOCIATION = 'unknown'
29
18
  const USAGE = 'Usage: josh measure:rerun <issue-number>'
30
19
  const NO_BASELINE = 'no `command → value` baseline found in ## ベースライン — nothing to re-run'
31
20
  const DATE_END = 10
32
- // One baseline command's budget — the same minute `josh-harness.ts` gives one command it spawns. A
33
- // baseline is a count or a digest that answers in seconds, so a command still running after a minute
34
- // is stuck, and without a bound it would hold the whole re-measurement open with nothing printed.
35
- const MEASURE_TIMEOUT_MS = 60_000
36
- const issue_schema = z.object({
37
- body: z.string().nullish(),
38
- author_association: z.string().nullish(),
39
- user: z.looseObject({ login: z.string() }).nullish(),
40
- })
41
-
42
- type IssueFields = z.infer<typeof issue_schema>
43
- type BodyRead = { kind: 'trusted'; body: string } | { kind: 'refused'; reason: string }
44
-
45
- // The command's stdout, trimmed to one value. A failing or timed-out command yields its message so the
46
- // pair still prints and the remaining baselines still run, rather than aborting the whole
47
- // re-measurement.
48
- function measure(command: string): string {
49
- try {
50
- return execSync(command, { encoding: 'utf8', timeout: MEASURE_TIMEOUT_MS }).trim()
51
- } catch (error) {
52
- return `(command failed: ${error_text.message_of(error)})`
53
- }
54
- }
55
21
 
56
22
  interface Outcome {
57
23
  pair: string
58
24
  ledger_line: string | undefined
59
25
  }
60
26
 
27
+ // A failing or timed-out command yields its message so the pair still prints and the remaining
28
+ // baselines still run, rather than aborting the whole re-measurement.
61
29
  function rerun_one(baseline: Baseline, date: string): Outcome {
62
- const after = measure(baseline.command)
30
+ const after = trusted_measure.text_of(trusted_measure.run_command(baseline.command))
63
31
  const is_unchanged = baseline_measure.is_no_change(baseline.value, after)
64
32
 
65
33
  return {
@@ -83,37 +51,6 @@ function today(now: Date): string {
83
51
  return now.toISOString().slice(0, DATE_END)
84
52
  }
85
53
 
86
- async function read_issue(issue_number: string): Promise<IssueFields | undefined> {
87
- const json = await git_gh_issue_read.issue_view_json(issue_number, ISSUE_FIELDS)
88
-
89
- return json === undefined ? undefined : parse_json_object_or_undefined(json, issue_schema)
90
- }
91
-
92
- function untrusted(issue_number: string, issue: IssueFields): BodyRead {
93
- const association = issue.author_association ?? UNKNOWN_ASSOCIATION
94
-
95
- return {
96
- kind: 'refused',
97
- reason: `refusing to run the baseline of ${session_cite.issue(issue_number)}: its author (${association}) is not ${gh_author_association.TRUSTED_LIST}`,
98
- }
99
- }
100
-
101
- // The body, only when its author can write to the repository. A read that fails is refused as well:
102
- // an unknown author is not a trusted one.
103
- async function read_trusted_body(issue_number: string): Promise<BodyRead> {
104
- const issue = await read_issue(issue_number)
105
-
106
- if (issue === undefined) {
107
- return { kind: 'refused', reason: `could not read issue ${session_cite.issue(issue_number)}` }
108
- }
109
-
110
- const author = { login: issue.user?.login, association: issue.author_association }
111
-
112
- if (!(await gh_author_association.is_trusted(author))) return untrusted(issue_number, issue)
113
-
114
- return { kind: 'trusted', body: issue.body ?? '' }
115
- }
116
-
117
54
  async function rerun(body: string, now: Date): Promise<number> {
118
55
  const baselines = baseline_measure.parse_baselines(body)
119
56
 
@@ -141,7 +78,7 @@ async function run(issue_number: string | undefined, now: Date): Promise<number>
141
78
  return FAILURE_EXIT_CODE
142
79
  }
143
80
 
144
- const read = await read_trusted_body(issue_number)
81
+ const read = await trusted_measure.read_trusted_body(issue_number)
145
82
 
146
83
  if (read.kind === 'refused') {
147
84
  console.error(read.reason)
@@ -50,6 +50,27 @@ function refusal_of(plan: string): string | undefined {
50
50
  return is_bare_none(entries) ? BARE_NONE : undefined
51
51
  }
52
52
 
53
- const plan_consumers_section = { HEADING, refusal_of }
53
+ // The lines `[start, end)` the section spans in `visible`, or `undefined` when the plan has none.
54
+ function section_range(visible: ReadonlyArray<string>): [number, number] | undefined {
55
+ const start = visible.findIndex((line) => line.trim() === HEADING)
56
+
57
+ if (start === -1) return undefined
58
+ const end = visible.slice(start + 1).findIndex((line) => ANY_HEADING.test(line.trim()))
59
+
60
+ return [start, end === -1 ? visible.length : start + 1 + end]
61
+ }
62
+
63
+ // `plan` with the section, heading included, removed — its entries name the files that read what the
64
+ // plan changes, not the files it edits (joshuafolkken/kit#4923).
65
+ function without(plan: string): string {
66
+ const range = section_range(markdown_section.unfenced_lines(plan))
67
+
68
+ if (range === undefined) return plan
69
+ const lines = markdown_section.lines_of(plan)
70
+
71
+ return [...lines.slice(0, range[0]), ...lines.slice(range[1])].join('\n')
72
+ }
73
+
74
+ const plan_consumers_section = { HEADING, refusal_of, without }
54
75
 
55
76
  export { plan_consumers_section }
@@ -10,6 +10,8 @@ const REPRODUCTION_HEADING = '## 再現'
10
10
  // A backticked command span on a line that is not inside a fenced block. `- `grep …`` and a bare
11
11
  // `` `grep …` `` both match; prose with no backticked command does not.
12
12
  const COMMAND_SPAN = /`[^`]+`/u
13
+ // A template placeholder span (a backticked `<...>`) the filer never replaced — not a command.
14
+ const PLACEHOLDER_SPAN = /`<[^`]*>`/gu
13
15
  // A fenced code block boundary — a backtick or tilde fence. The actual output goes between an opening
14
16
  // and a closing fence, so a valid reproduction carries at least two of these lines. Tilde is accepted
15
17
  // so the template can show the block without nesting a backtick fence inside its own ```md example.
@@ -28,7 +30,7 @@ function has_command_line(lines: ReadonlyArray<string>): boolean {
28
30
  const first_fence = lines.findIndex((line) => FENCE_LINE.test(line))
29
31
  const open_lines = first_fence === -1 ? lines : lines.slice(0, first_fence)
30
32
 
31
- return open_lines.some((line) => COMMAND_SPAN.test(line))
33
+ return open_lines.some((line) => COMMAND_SPAN.test(line.replaceAll(PLACEHOLDER_SPAN, '')))
32
34
  }
33
35
 
34
36
  // An opened-and-closed output block is two fence lines, so counting them is enough — a section with a
@@ -0,0 +1,90 @@
1
+ import { split_assess, type SizeMeasurement } from '#scripts/split/split-assess'
2
+ import { z } from 'zod'
3
+ import { SPLIT_ROUTE_LABEL } from './issue-labels'
4
+ import { markdown_section } from './markdown-section'
5
+
6
+ // joshuafolkken/kit#4675: a request clearly over the split guide reached a lane as one Issue because
7
+ // nothing asked for its size when it was filed, and a split is cheapest at filing time. Every filing
8
+ // declares its estimate under the background heading, so it stays on the Issue; one over the guide is
9
+ // refused unless it is filed as a split or says why it stays one Issue.
10
+ //
11
+ // **The guide is `split_assess`'s, never a copy** — the measured diff and the declared estimate are
12
+ // judged by one `verdict_for`. An epic (a body carrying `## Split rationale`) declares no estimate:
13
+ // its children do.
14
+
15
+ const BACKGROUND_HEADING = '## 背景'
16
+ const EPIC_HEADING = '## Split rationale'
17
+ const PREFIX = '- 規模見積もり:'
18
+ const { REASON_PREFIX } = split_assess
19
+ const DECLARATION = `${PREFIX} 約 <N> ファイル / <M> 行(テスト除く)`
20
+ // Full- or half-width parentheses alike: the two read the same to the person typing the line.
21
+ const VALUE_PATTERN =
22
+ /^約\s*(?<files>\d+)\s*ファイル\s*\/\s*(?<lines>\d+)\s*行\s*[((]テスト除く[))]$/u
23
+ const MISSING = `missing size estimate: declare \`${DECLARATION}\` under ${BACKGROUND_HEADING}`
24
+ // The refusal ends in a line ready to paste (joshuafolkken/kit#5225).
25
+ const EXAMPLE = `${PREFIX} 約 3 ファイル / 60 行(テスト除く)`
26
+ const MALFORMED = `\`${PREFIX}\` must be written ${DECLARATION}\n${EXAMPLE}`
27
+ const estimate_schema = z.object({ files: z.coerce.number(), lines: z.coerce.number() })
28
+
29
+ function declared(body: string, prefix: string): string | undefined {
30
+ const background = markdown_section.section_lines(body, BACKGROUND_HEADING).join('\n')
31
+ const line = markdown_section
32
+ .unfenced_lines(background)
33
+ .map((text) => text.trim())
34
+ .find((text) => text.startsWith(prefix))
35
+
36
+ return line?.slice(prefix.length).trim()
37
+ }
38
+
39
+ function is_epic(body: string): boolean {
40
+ return markdown_section.has_line(body, EPIC_HEADING)
41
+ }
42
+
43
+ // The declared estimate judged against the guide, or `undefined` when there is none or it does not parse.
44
+ function parse(body: string): SizeMeasurement | undefined {
45
+ const read = estimate_schema.safeParse(VALUE_PATTERN.exec(declared(body, PREFIX) ?? '')?.groups)
46
+
47
+ if (!read.success) return undefined
48
+ const { files, lines } = read.data
49
+
50
+ return { files, lines, verdict: split_assess.verdict_for(files, lines) }
51
+ }
52
+
53
+ // The shape check `issue:lint` makes: the declaration is there and parses. The guide needs the route,
54
+ // which only `issue:file` knows, so it is `guide_problem`'s.
55
+ function problems(body: string): ReadonlyArray<string> {
56
+ if (is_epic(body)) return []
57
+ if (declared(body, PREFIX) === undefined) return [MISSING]
58
+
59
+ return parse(body) === undefined ? [MALFORMED] : []
60
+ }
61
+
62
+ // A split filing, or one that declares why it stays one Issue, passes over the guide.
63
+ function is_excused(body: string, route: string | undefined): boolean {
64
+ return route === SPLIT_ROUTE_LABEL || (declared(body, REASON_PREFIX) ?? '') !== ''
65
+ }
66
+
67
+ function refusal(estimate: SizeMeasurement): string {
68
+ const counted = `size estimate ${String(estimate.files)} file(s) / ${String(estimate.lines)} line(s) exceeds the guide (${String(split_assess.FILE_GUIDE)} files / ${String(split_assess.LINE_GUIDE)} lines)`
69
+
70
+ return `${counted} — file it split (\`--route split\`) or declare \`${REASON_PREFIX} <理由>\`; split-assessment.md → "${split_assess.QUESTION_SECTION}"`
71
+ }
72
+
73
+ // The filing refusal for an estimate over the guide; `undefined` under it, on a split filing, or with
74
+ // the reason declared. A missing or malformed estimate is `problems`' refusal, not this one.
75
+ function guide_problem(body: string, route: string | undefined): string | undefined {
76
+ const estimate = parse(body)
77
+
78
+ if (estimate?.verdict !== split_assess.SPLIT_VERDICT) return undefined
79
+
80
+ return is_excused(body, route) ? undefined : refusal(estimate)
81
+ }
82
+
83
+ // The declared file count, or `undefined` when there is no estimate or it does not parse.
84
+ function declared_files(body: string): number | undefined {
85
+ return parse(body)?.files
86
+ }
87
+
88
+ const size_estimate = { declared_files, guide_problem, problems }
89
+
90
+ export { size_estimate }