@joshuafolkken/kit 1.1178.0 → 1.1240.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 (442) hide show
  1. package/.claude/.claude-plugin/plugin.json +1 -1
  2. package/.claude/settings.json +2 -2
  3. package/.claude/skills/dependency-update/SKILL.md +4 -8
  4. package/.claude/skills/epic-commands/SKILL.md +2 -2
  5. package/.claude/skills/epic-commands/epic-bundle.md +1 -1
  6. package/.claude/skills/workflow-commands/SKILL.md +2 -4
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +7 -3
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +9 -4
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +4 -3
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +21 -13
  13. package/.claude/skills/workflow-commands/chain-rule.md +1 -1
  14. package/.claude/skills/workflow-commands/delegation.md +2 -2
  15. package/.claude/skills/workflow-commands/followup.md +10 -0
  16. package/.claude/skills/workflow-commands/fullrun-steps.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +1 -1
  18. package/.claude/skills/workflow-commands/issue-scout.md +1 -1
  19. package/.claude/skills/workflow-commands/kickoff.md +2 -1
  20. package/.claude/skills/workflow-commands/latest-gate.md +1 -1
  21. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  22. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  23. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  24. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  25. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  26. package/.codex/hooks.json +2 -2
  27. package/.cursorrules +6 -41
  28. package/.github/actions/setup-node/action.yml +1 -1
  29. package/.github/workflows/auto-tag.yml +3 -4
  30. package/.github/workflows/ci.yml +32 -31
  31. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  32. package/.github/workflows/publish.yml +50 -4
  33. package/.github/workflows/sonar-qube.yml +3 -0
  34. package/.pnpmfile.mjs +4 -3
  35. package/.prettierignore +8 -2
  36. package/CLAUDE.md +18 -20
  37. package/README.md +44 -38
  38. package/SECURITY.md +15 -1
  39. package/dist/CLAUDE.md +18 -20
  40. package/dist/commands/document-section-cli.js +734 -0
  41. package/dist/hooks/audit-provision.js +346 -0
  42. package/dist/hooks/batch-guard.js +5 -4
  43. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-4TZVB44A.js} +496 -378
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  46. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  47. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-F3ELQ2AF.js} +3 -3
  48. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-GPTFMK5E.js} +1 -1
  49. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  50. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-KHGB5S3Y.js} +7 -6
  51. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-KHX4U3BG.js} +735 -683
  52. package/dist/hooks/{chunk-VESKRQOF.js → chunk-KRBERBBI.js} +3 -3
  53. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-QVSWU24O.js} +4 -4
  54. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-WK2XSHSI.js} +1643 -923
  55. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-XZYKEM6J.js} +25 -10
  56. package/dist/hooks/{chunk-32GQTL4F.js → chunk-ZQGD4GBY.js} +4 -2
  57. package/dist/hooks/codex-hook-adapter.js +14 -12
  58. package/dist/hooks/duplicate-read-guard.js +6 -5
  59. package/dist/hooks/format-edited.js +4 -3
  60. package/dist/hooks/inputs.json +203 -179
  61. package/dist/hooks/investigation-guard.js +6 -5
  62. package/dist/hooks/pretool-guard.js +12 -10
  63. package/dist/hooks/session-lang.js +2 -4
  64. package/dist/hooks/stop-guard.js +66 -72
  65. package/dist/josh.js +1088 -78
  66. package/dist/version/index.d.ts +7 -2
  67. package/dist/version/index.js +152 -75
  68. package/eslint/base.js +23 -1
  69. package/eslint/rules/code-quality.js +3 -0
  70. package/eslint/rules/early-return-one-liner.js +98 -0
  71. package/eslint/rules/unicorn.js +16 -1
  72. package/package.json +7 -6
  73. package/pnpm-workspace.yaml +9 -4
  74. package/prettier/basic.d.ts +3 -1
  75. package/prettier/index.d.ts +3 -1
  76. package/prettier/static.d.ts +3 -1
  77. package/prompts/coding-standards.md +1 -1
  78. package/prompts/collaboration-workflow/file-edits.md +4 -4
  79. package/prompts/collaboration-workflow/glossary.md +1 -1
  80. package/prompts/collaboration-workflow/issue-template.md +1 -0
  81. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  82. package/prompts/collaboration-workflow/overview.md +2 -2
  83. package/prompts/collaboration-workflow/principles.md +14 -12
  84. package/prompts/collaboration-workflow/report-format.md +1 -1
  85. package/prompts/collaboration-workflow/residency.md +1 -1
  86. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  91. package/prompts/review-rubric.md +1 -2
  92. package/prompts/review.md +1 -1
  93. package/scripts/agent/agent-headless.ts +29 -0
  94. package/scripts/agent/agent-launch-environment.ts +6 -2
  95. package/scripts/agent/codex-agent-argv.ts +2 -1
  96. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  97. package/scripts/backlog/backlog-arrival.ts +3 -3
  98. package/scripts/backlog/backlog-budget.ts +1 -1
  99. package/scripts/backlog/backlog-drive-args.ts +135 -0
  100. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  101. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  102. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  103. package/scripts/backlog/backlog-drive-named.ts +3 -3
  104. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  105. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  106. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  107. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  108. package/scripts/backlog/backlog-drive.ts +1 -1
  109. package/scripts/backlog/backlog-next.ts +16 -5
  110. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  111. package/scripts/backlog/backlog-offer.ts +1 -1
  112. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  113. package/scripts/backlog/backlog-pool.ts +57 -11
  114. package/scripts/backlog/backlog-ready.ts +1 -1
  115. package/scripts/backlog/backlog-scope.ts +1 -1
  116. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  117. package/scripts/backlog/backlog-stalled.ts +1 -1
  118. package/scripts/build/build-codex-hooks.ts +3 -1
  119. package/scripts/build/build-commands.ts +66 -0
  120. package/scripts/build/build-hooks.ts +2 -1
  121. package/scripts/build/build-library.ts +12 -3
  122. package/scripts/build/build.ts +70 -0
  123. package/scripts/build/local-bin.ts +3 -13
  124. package/scripts/delegation/investigation-reads.ts +3 -1
  125. package/scripts/discovery/repo-origin.ts +5 -5
  126. package/scripts/doctor/doctor-consumer.ts +2 -2
  127. package/scripts/doctor/doctor-io.ts +2 -3
  128. package/scripts/document/capped-print-part.ts +30 -0
  129. package/scripts/document/capped-print.ts +3 -4
  130. package/scripts/document/document-byte-budget.ts +11 -2
  131. package/scripts/document/document-scan.ts +117 -0
  132. package/scripts/document/document-section.ts +143 -16
  133. package/scripts/document/entry-read-budget.ts +5 -3
  134. package/scripts/document/entry-read-set.ts +10 -89
  135. package/scripts/document/read-set-cost.ts +98 -0
  136. package/scripts/epic/epic-add-body.ts +1 -0
  137. package/scripts/epic/epic-audit-checks.ts +1 -1
  138. package/scripts/epic/epic-audit-rationale.ts +4 -3
  139. package/scripts/epic/epic-bundle-cli.ts +14 -3
  140. package/scripts/epic/epic-bundle-poll.ts +41 -0
  141. package/scripts/epic/epic-busy.ts +1 -1
  142. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  143. package/scripts/epic/epic-classify.ts +1 -1
  144. package/scripts/epic/epic-cli-argv.ts +78 -46
  145. package/scripts/epic/epic-cli-remove.ts +11 -19
  146. package/scripts/epic/epic-cli.ts +74 -73
  147. package/scripts/epic/epic-close-comment.ts +1 -1
  148. package/scripts/epic/epic-close.ts +1 -1
  149. package/scripts/epic/epic-cross-repo.ts +5 -5
  150. package/scripts/epic/epic-graph.ts +1 -1
  151. package/scripts/epic/epic-index.ts +1 -1
  152. package/scripts/epic/epic-issue.ts +2 -6
  153. package/scripts/epic/epic-lane-offer.ts +2 -2
  154. package/scripts/epic/epic-next.ts +29 -42
  155. package/scripts/epic/epic-solo-stale.ts +1 -1
  156. package/scripts/epic/epic-triage.ts +8 -4
  157. package/scripts/epic/epic-validate.ts +1 -1
  158. package/scripts/followup/git-followup-finish.ts +2 -2
  159. package/scripts/followup/git-followup-label.ts +2 -2
  160. package/scripts/followup/git-followup-merged.ts +2 -2
  161. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  162. package/scripts/followup/git-pr-followup.ts +4 -129
  163. package/scripts/gate/core-admission.ts +162 -0
  164. package/scripts/gate/core-budget.ts +190 -68
  165. package/scripts/gate/gate-plan.ts +80 -7
  166. package/scripts/gate/gate-report.ts +28 -9
  167. package/scripts/gate/gate-skip.ts +38 -7
  168. package/scripts/gate/hook-gate-reuse.ts +12 -10
  169. package/scripts/gate/machine-capacity.ts +175 -0
  170. package/scripts/gate/pre-commit-type-check.ts +4 -1
  171. package/scripts/gate/project-checks.ts +6 -9
  172. package/scripts/gate/type-check-step.ts +2 -3
  173. package/scripts/gate/verification-gate.ts +34 -10
  174. package/scripts/gh/git-gh-api-path.ts +2 -2
  175. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  176. package/scripts/gh/git-gh-issue-write.ts +53 -7
  177. package/scripts/gh/git-gh-pr-read.ts +64 -19
  178. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  179. package/scripts/gh/git-gh-pr.ts +5 -1
  180. package/scripts/gh/git-pr-ai-review.ts +1 -1
  181. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  182. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  183. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  184. package/scripts/gh/git-pr-checks.ts +7 -5
  185. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  186. package/scripts/gh/git-pr-error.ts +1 -3
  187. package/scripts/gh/git-pr.ts +9 -13
  188. package/scripts/gh/github-issue-url.ts +2 -2
  189. package/scripts/git/constants.ts +2 -6
  190. package/scripts/git/git-command.ts +20 -218
  191. package/scripts/git/git-diff-reads.ts +225 -0
  192. package/scripts/git/git-error.ts +4 -12
  193. package/scripts/git/git-pre-push-hook.ts +139 -0
  194. package/scripts/git/git-prompt.ts +2 -6
  195. package/scripts/git/git-spawn-sync.ts +3 -1
  196. package/scripts/git/git-spawn.ts +1 -1
  197. package/scripts/git/git-staging.ts +4 -6
  198. package/scripts/git/git-status.ts +3 -9
  199. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  200. package/scripts/hooks/edited-cspell.ts +2 -4
  201. package/scripts/hooks/format-edited-file.ts +3 -12
  202. package/scripts/hooks/pre-push-unit.ts +31 -9
  203. package/scripts/hooks/reserved-run.ts +2 -2
  204. package/scripts/hooks/run-hook.sh +5 -4
  205. package/scripts/hooks/stop-guard.ts +5 -3
  206. package/scripts/init/init-actions.ts +0 -5
  207. package/scripts/init/init-ai-copy.ts +13 -2
  208. package/scripts/init/init-logic-json-merge.ts +19 -0
  209. package/scripts/init/init-logic-secretlint.ts +7 -7
  210. package/scripts/init/init-logic-workspace.ts +41 -2
  211. package/scripts/init/init-logic.ts +16 -26
  212. package/scripts/init/init.ts +10 -16
  213. package/scripts/init/initial-commit.ts +4 -4
  214. package/scripts/init/kit-development-versions.ts +40 -0
  215. package/scripts/init/kit-setup-state.ts +2 -2
  216. package/scripts/init/package-manager-pin.ts +35 -0
  217. package/scripts/init/start-exec.ts +68 -19
  218. package/scripts/init/start-setup-pr.ts +9 -9
  219. package/scripts/init/start-steps.ts +6 -10
  220. package/scripts/issue/defect-rate-cli.ts +1 -1
  221. package/scripts/issue/git-next-issues.ts +1 -1
  222. package/scripts/issue/issue-auto-ok.ts +32 -7
  223. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  224. package/scripts/issue/issue-backlinks.ts +9 -9
  225. package/scripts/issue/issue-comment-cli.ts +19 -13
  226. package/scripts/issue/issue-file-cli.ts +72 -22
  227. package/scripts/issue/issue-file.ts +29 -1
  228. package/scripts/issue/issue-labels.ts +13 -0
  229. package/scripts/issue/issue-release-cli.ts +155 -0
  230. package/scripts/issue/issue-release.ts +58 -0
  231. package/scripts/issue/issue-scout-closed.ts +1 -1
  232. package/scripts/issue/issue-state-cli.ts +30 -74
  233. package/scripts/josh/josh-command-map.ts +2 -0
  234. package/scripts/josh/josh-command-types.ts +14 -0
  235. package/scripts/josh/josh-commands-ai.ts +4 -4
  236. package/scripts/josh/josh-commands-backlog.ts +4 -1
  237. package/scripts/josh/josh-commands-development.ts +18 -0
  238. package/scripts/josh/josh-commands-document.ts +1 -0
  239. package/scripts/josh/josh-commands-issue.ts +8 -1
  240. package/scripts/josh/josh-commands-lane.ts +24 -1
  241. package/scripts/josh/josh-commands-maintenance.ts +6 -6
  242. package/scripts/josh/josh-commands-run.ts +12 -7
  243. package/scripts/josh/josh-in-process.ts +28 -9
  244. package/scripts/josh/josh-logic.ts +34 -15
  245. package/scripts/josh/process-identity.ts +1 -3
  246. package/scripts/josh/session-language-cli.ts +3 -4
  247. package/scripts/lane/lane-await.ts +5 -1
  248. package/scripts/lane/lane-child-invocation.ts +2 -2
  249. package/scripts/lane/lane-cli.ts +12 -4
  250. package/scripts/lane/lane-dispatch.ts +14 -10
  251. package/scripts/lane/lane-environment.ts +5 -1
  252. package/scripts/lane/lane-handoff.ts +1 -1
  253. package/scripts/lane/lane-install.ts +2 -13
  254. package/scripts/lane/lane-launch-cli.ts +15 -1
  255. package/scripts/lane/lane-ledger.ts +145 -0
  256. package/scripts/lane/lane-leftover.ts +44 -30
  257. package/scripts/lane/lane-load.ts +108 -0
  258. package/scripts/lane/lane-measure-cli.ts +149 -0
  259. package/scripts/lane/lane-occupancy.ts +3 -0
  260. package/scripts/lane/lane-open.ts +5 -5
  261. package/scripts/lane/lane-paths.ts +13 -0
  262. package/scripts/lane/lane-reap.ts +1 -3
  263. package/scripts/lane/lane-registry.ts +11 -0
  264. package/scripts/lane/lane-relaunch.ts +1 -1
  265. package/scripts/lane/lane-stats.ts +224 -0
  266. package/scripts/lane/lane-stray.ts +140 -0
  267. package/scripts/lane/lane-vacant.ts +36 -0
  268. package/scripts/lane/openai-lane-ship.ts +2 -2
  269. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  270. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  271. package/scripts/lane/openai-review-broker.ts +2 -4
  272. package/scripts/lib/ancestor-directories.ts +35 -0
  273. package/scripts/lib/animation-helpers.ts +2 -2
  274. package/scripts/lib/buffered-process.ts +3 -7
  275. package/scripts/lib/cli-flags.ts +71 -1
  276. package/scripts/lib/spawn-exit.ts +2 -2
  277. package/scripts/lib/timeouts.ts +35 -2
  278. package/scripts/lines/line-budget.ts +4 -6
  279. package/scripts/notify/telegram-test.ts +6 -0
  280. package/scripts/observations/observation-ledger-line.ts +6 -6
  281. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  282. package/scripts/observations/observation-ledger.ts +26 -7
  283. package/scripts/overrides/lockfile-overrides.ts +1 -1
  284. package/scripts/overrides/overrides-logic.ts +1 -1
  285. package/scripts/propagate/propagate-git.ts +1 -1
  286. package/scripts/propagate/propagate-publish.ts +2 -2
  287. package/scripts/propagate/propagate-steps.ts +20 -15
  288. package/scripts/propagate/propagate.ts +36 -27
  289. package/scripts/refactor/import-graph.ts +18 -1
  290. package/scripts/refactor/refactor-lint.ts +4 -4
  291. package/scripts/release/github-release-environment.ts +1 -0
  292. package/scripts/release/github-release-runs.ts +115 -0
  293. package/scripts/release/github-release.ts +13 -67
  294. package/scripts/repo/repository-labels.ts +2 -2
  295. package/scripts/repo/required-checks-logic.ts +1 -3
  296. package/scripts/review/live-evidence.ts +2 -4
  297. package/scripts/review/review-brief-cli.ts +1 -1
  298. package/scripts/review/review-finding-ledger.ts +1 -1
  299. package/scripts/review/review-findings-cli.ts +1 -3
  300. package/scripts/rules/decision-oracle-batch.ts +128 -0
  301. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  302. package/scripts/rules/decision-oracle.ts +8 -120
  303. package/scripts/rules/delivered-rules.ts +26 -182
  304. package/scripts/rules/direct-filing.ts +1 -1
  305. package/scripts/rules/early-heartbeat.ts +3 -3
  306. package/scripts/rules/git-argv.ts +1 -1
  307. package/scripts/rules/git-force.ts +1 -1
  308. package/scripts/rules/implementation-cut.ts +2 -2
  309. package/scripts/rules/index-guard.ts +1 -1
  310. package/scripts/rules/issue-comments.ts +118 -0
  311. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  312. package/scripts/rules/lane-background.ts +2 -2
  313. package/scripts/rules/lane-park.ts +18 -0
  314. package/scripts/rules/lane-split-park.ts +128 -0
  315. package/scripts/rules/oracle-firing.ts +1 -1
  316. package/scripts/rules/poll-loop.ts +1 -1
  317. package/scripts/rules/pre-gate-cut.ts +1 -1
  318. package/scripts/rules/protected-files.ts +59 -4
  319. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  320. package/scripts/rules/shell-body-trigger.ts +1 -1
  321. package/scripts/rules/stop-rules.ts +18 -7
  322. package/scripts/rules/test-declared-commit.ts +4 -4
  323. package/scripts/rules/wip-cap.ts +63 -0
  324. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  325. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  326. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  327. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  328. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  329. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +18 -5
  330. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  331. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  332. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  333. package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +1 -0
  334. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  335. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  336. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +20 -2
  337. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  338. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  339. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  340. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  341. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  342. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  343. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  344. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  345. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  346. package/scripts/run/run-ending.ts +2 -2
  347. package/scripts/run/run-halfrun-resume.ts +6 -1
  348. package/scripts/run/run-headless.ts +1 -1
  349. package/scripts/run/run-invocation.ts +85 -84
  350. package/scripts/run/run-liveness.ts +1 -1
  351. package/scripts/run/run-parent-cut-hook.ts +1 -1
  352. package/scripts/run/run-preflight.ts +6 -6
  353. package/scripts/run/run-prep-cli.ts +1 -1
  354. package/scripts/run/run-prrun-resume.ts +1 -1
  355. package/scripts/run/run-report-cli.ts +4 -4
  356. package/scripts/run/run-report.ts +2 -2
  357. package/scripts/run/run-status-cli.ts +1 -1
  358. package/scripts/run/run-step-cli.ts +3 -3
  359. package/scripts/run/run-step.ts +7 -4
  360. package/scripts/run/run-stop-notify.ts +1 -1
  361. package/scripts/run/run-stranded-detect.ts +4 -4
  362. package/scripts/run/run-stranded.ts +2 -2
  363. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  364. package/scripts/run/run-watcher-guard.ts +5 -3
  365. package/scripts/run/run-watcher-hook.ts +1 -1
  366. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  367. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  368. package/scripts/run/ship/run-ship-lock.ts +50 -0
  369. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  370. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  371. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  372. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  373. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  374. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  375. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  376. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  377. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  378. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +25 -11
  379. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +58 -7
  380. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  381. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  382. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +13 -15
  383. package/scripts/run/wake/run-wake-restart.ts +58 -0
  384. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  385. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +16 -65
  386. package/scripts/safe-chain/preinstall-command.ts +20 -21
  387. package/scripts/safe-chain/project-config.ts +19 -15
  388. package/scripts/sync/sync-ai-files.ts +1 -1
  389. package/scripts/sync/sync-configs.ts +2 -1
  390. package/scripts/sync/sync.ts +15 -2
  391. package/scripts/test/unit-worker-share.ts +1 -0
  392. package/scripts/time-runtime/time-background.ts +5 -1
  393. package/scripts/time-runtime/time-instant.ts +3 -1
  394. package/scripts/time-runtime/time-spans.ts +6 -139
  395. package/scripts/time-runtime/time-tool-call.ts +160 -0
  396. package/scripts/version/effective-upstream.ts +2 -20
  397. package/scripts/version/latest-corepack.ts +124 -93
  398. package/scripts/version/latest-regression.ts +1 -1
  399. package/scripts/version/latest-update.ts +4 -4
  400. package/scripts/version/npm-registry.ts +20 -5
  401. package/scripts/version/publishable-range-check.ts +2 -2
  402. package/scripts/version/publishable-range.ts +1 -1
  403. package/scripts/version/release-age.ts +48 -38
  404. package/scripts/version/release-hold.ts +1 -1
  405. package/scripts/version/safe-chain-version-update.ts +40 -0
  406. package/scripts/version/upgrade-shell-command.ts +48 -8
  407. package/scripts/version/version-check-logic.ts +23 -14
  408. package/scripts/version/version-commands.ts +23 -11
  409. package/scripts/version/version-remote.ts +28 -13
  410. package/templates/pnpm-workspace.basic.yaml +4 -0
  411. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  412. package/templates/workflows/github-release.yml +6 -0
  413. package/scripts/build/build-config-merge.ts +0 -27
  414. package/scripts/build/build-managed-marker.ts +0 -27
  415. package/scripts/build/build-self-sync-guard.ts +0 -27
  416. package/scripts/build/build-version.ts +0 -26
  417. package/scripts/version/preinstall-version-update.ts +0 -86
  418. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  419. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  420. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  421. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  422. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  423. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  424. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  425. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  426. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  427. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  428. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  429. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  430. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  431. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  432. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  433. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  434. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  435. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  436. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  437. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  438. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  439. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  440. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  441. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  442. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -13,6 +13,7 @@
13
13
  // moved to "read it later" is a rule that measurably never fires, and this moves nothing to later.
14
14
 
15
15
  import { file_reader } from '#scripts/lib/read-file'
16
+ import { document_scan } from './document-scan'
16
17
 
17
18
  // `#` to `######`, one space, then the title. Written with a literal space rather than `\s+` and a
18
19
  // lazy title so it cannot backtrack: `sonarjs/super-linear-regex` rejects the `\s+…+?\s*$` shape,
@@ -21,6 +22,17 @@ const HEADING_PATTERN = /^(?<hashes>#{1,6}) (?<title>.*)$/u
21
22
  const FENCE = '```'
22
23
  const SINGLE_MATCH = 1
23
24
 
25
+ // Any bold span is a citable anchor: the documents point at `- **Cross-package problems → …**` at a
26
+ // line start and at a `**…**` emphasis mid-paragraph alike.
27
+ const BOLD_LABEL_PATTERN = /\*\*([^*]+?)\*\*/gu
28
+ const BOLD_DELIMITER = '**'
29
+ // A one-line inline code span: a double-backtick span (which may quote a backtick) or a single one.
30
+ const CODE_SPAN_PATTERN = /``[^\n]*?``|`[^`\n]*`/gu
31
+ // A label ranks below every heading, so a heading section never ends at one.
32
+ const LABEL_LEVEL = 7
33
+ // A list item opens with `-`, `*` or a number, after any indentation.
34
+ const LIST_ITEM_PATTERN = /^(?<indent>\s*)(?:[-*]|\d+\.) /u
35
+
24
36
  interface Heading {
25
37
  level: number
26
38
  title: string
@@ -58,13 +70,27 @@ function push_heading(found: Array<Heading>, line: string, index: number): void
58
70
  // **Fenced blocks are skipped, and that is not defensive tidying.** These documents quote shell, and
59
71
  // a shell comment at the start of a line is a `#` in column one. Counted as a heading it would end
60
72
  // the section above it early, which is a silent wrong answer rather than a visible failure.
73
+ // One flag per line — `true` for a fence delimiter and every line between a pair of them — so the
74
+ // heading scan and the label scan cannot disagree on what is quoted code.
75
+ function fenced_flags(lines: ReadonlyArray<string>): Array<boolean> {
76
+ let is_inside_fence = false
77
+
78
+ return lines.map(function is_fenced(line: string): boolean {
79
+ if (!line.startsWith(FENCE)) return is_inside_fence
80
+
81
+ is_inside_fence = !is_inside_fence
82
+
83
+ return true
84
+ })
85
+ }
86
+
61
87
  function headings(markdown: string): Array<Heading> {
88
+ const lines = markdown.split('\n')
89
+ const fenced = fenced_flags(lines)
62
90
  const found: Array<Heading> = []
63
- let is_inside_fence = false
64
91
 
65
- for (const [index, line] of markdown.split('\n').entries()) {
66
- if (line.startsWith(FENCE)) is_inside_fence = !is_inside_fence
67
- else if (!is_inside_fence) push_heading(found, line, index)
92
+ for (const [index, line] of lines.entries()) {
93
+ if (fenced[index] !== true) push_heading(found, line, index)
68
94
  }
69
95
 
70
96
  return found
@@ -74,24 +100,77 @@ function titles(markdown: string): Array<string> {
74
100
  return headings(markdown).map((heading) => heading.title)
75
101
  }
76
102
 
103
+ // The zero-based line each character offset sits on, read as the number of line starts at or before it.
104
+ function line_starts(markdown: string): Array<number> {
105
+ return [...markdown.matchAll(/\n/gu)].map((match) => match.index + SINGLE_MATCH)
106
+ }
107
+
108
+ function line_of(starts: ReadonlyArray<number>, offset: number): number {
109
+ return starts.filter((start) => start <= offset).length
110
+ }
111
+
112
+ // **A bold label is an anchor too** (joshuafolkken/kit#3248). The documents cite
113
+ // `- **Output language follows …**` exactly as they cite a heading, and the reference tests accepted
114
+ // both while this reader found only headings — so a green suite promised a section the command then
115
+ // refused. A label may wrap across lines, so the scan runs over the whole text, not line by line.
116
+ //
117
+ // Fenced lines are blanked before the scan rather than filtered after it: a `docs/**` glob in a
118
+ // fence would otherwise take a delimiter and pair every later `**` with the wrong partner. Blanking
119
+ // keeps the line count, so a label's line number still indexes the original document.
120
+ function unfenced_text(markdown: string): string {
121
+ const lines = markdown.split('\n')
122
+ const fenced = fenced_flags(lines)
123
+
124
+ return lines.map((line, index) => (fenced[index] === true ? '' : line)).join('\n')
125
+ }
126
+
127
+ // An inline code span gets the same treatment one level down: `prompts/**` mid-paragraph would take a
128
+ // delimiter just as a fenced glob does. Its `*` become spaces, so every offset still indexes the
129
+ // unmasked text — which is where the title is read from, keeping a label that quotes code intact.
130
+ function code_masked(text: string): string {
131
+ return text.replaceAll(CODE_SPAN_PATTERN, (span) => span.replaceAll('*', ' '))
132
+ }
133
+
134
+ function labels(markdown: string): Array<Heading> {
135
+ const text = unfenced_text(markdown)
136
+ const starts = line_starts(text)
137
+
138
+ return [...code_masked(text).matchAll(BOLD_LABEL_PATTERN)].map((match) => ({
139
+ level: LABEL_LEVEL,
140
+ title: text.slice(
141
+ match.index + BOLD_DELIMITER.length,
142
+ match.index + match[0].length - BOLD_DELIMITER.length,
143
+ ),
144
+ line: line_of(starts, match.index),
145
+ }))
146
+ }
147
+
77
148
  // **Prefix, because a reference cites the name and the heading carries the name plus its gloss.**
78
149
  // `` → "The hand-off" `` points at `## The hand-off — one session does not have to run the whole
79
150
  // epic`, and `` → "The check is asked at every merge" `` at a heading that continues with a comma.
80
151
  // Requiring the whole heading would make every such reference unresolvable, and citing the whole
81
- // heading in the prose would put the gloss into every pointer.
152
+ // heading in the prose would put the gloss into every pointer. Both sides are compared in the forms
153
+ // `document_scan.reference_forms` gives, so a reference that wraps or drops a heading's number lands.
82
154
  function is_match(heading: Heading, wanted: string): boolean {
83
- return heading.title.startsWith(wanted)
155
+ return document_scan.reference_forms(heading.title).some((form) => form.startsWith(wanted))
84
156
  }
85
157
 
86
158
  function prefixed(found: ReadonlyArray<Heading>, wanted: string): Array<Heading> {
87
- return found.filter((heading) => is_match(heading, wanted))
159
+ const normalized = document_scan.normalize_reference(wanted)
160
+
161
+ return found.filter((heading) => is_match(heading, normalized))
162
+ }
163
+
164
+ function is_exact(heading: Heading, wanted: string): boolean {
165
+ return document_scan.reference_forms(heading.title).includes(wanted)
88
166
  }
89
167
 
90
168
  // **Exact wins, and an ambiguous prefix is refused rather than ranked.** Two headings that both
91
169
  // start with the cited text is a document whose reference is genuinely ambiguous; picking the first
92
170
  // would hand back a section nobody named, which is a failure a whole-file read does not have.
93
171
  function select(found: ReadonlyArray<Heading>, wanted: string): Heading | undefined {
94
- const exact = found.find((heading) => heading.title === wanted)
172
+ const normalized = document_scan.normalize_reference(wanted)
173
+ const exact = found.find((heading) => is_exact(heading, normalized))
95
174
 
96
175
  if (exact !== undefined) return exact
97
176
 
@@ -103,34 +182,82 @@ function select(found: ReadonlyArray<Heading>, wanted: string): Heading | undefi
103
182
  // A section ends where the next heading of the same or higher rank begins, so a `##` section carries
104
183
  // its `###` subsections with it — which is what a pointer at a `##` heading means.
105
184
  function last_line_of(found: ReadonlyArray<Heading>, chosen: Heading, total_lines: number): number {
106
- const index = found.indexOf(chosen)
107
- const next = found.slice(index + SINGLE_MATCH).find((heading) => heading.level <= chosen.level)
185
+ const next = found.find((heading) => heading.line > chosen.line && heading.level <= chosen.level)
108
186
 
109
187
  return (next === undefined ? total_lines : next.line) - SINGLE_MATCH
110
188
  }
111
189
 
112
- function section(markdown: string, wanted: string): Section | undefined {
113
- const lines = markdown.split('\n')
190
+ function indent_of(line: string): number {
191
+ return line.length - line.trimStart().length
192
+ }
193
+
194
+ // Where the block a label sits in stops: a blank line, a heading, or a list item at the label line's
195
+ // own depth or shallower — so a label's nested sub-items come with it and its next sibling does not.
196
+ function is_sibling_item(line: string, depth: number): boolean {
197
+ const { indent } = LIST_ITEM_PATTERN.exec(line)?.groups ?? {}
198
+
199
+ return indent !== undefined && indent.length <= depth
200
+ }
201
+
202
+ function ends_block(line: string, depth: number): boolean {
203
+ if (line.trim() === '' || HEADING_PATTERN.test(line)) return true
204
+
205
+ return is_sibling_item(line, depth)
206
+ }
207
+
208
+ // A label's section is the paragraph or list item it opens, not the rest of the document.
209
+ function label_last_line(lines: ReadonlyArray<string>, label: Heading): number {
210
+ const depth = indent_of(lines[label.line] ?? '')
211
+ const end = lines.findIndex((line, index) => index > label.line && ends_block(line, depth))
212
+
213
+ return (end === -1 ? lines.length : end) - SINGLE_MATCH
214
+ }
215
+
216
+ // **A heading answers before any label does.** A label is consulted only when no heading even
217
+ // starts with the cited text, so an ambiguous heading reference is refused rather than rescued by a
218
+ // bold span that happens to share its prefix.
219
+ function choose(markdown: string, wanted: string): Heading | undefined {
114
220
  const found = headings(markdown)
115
- const chosen = select(found, wanted)
221
+
222
+ if (prefixed(found, wanted).length > 0) return select(found, wanted)
223
+
224
+ return select(labels(markdown), wanted)
225
+ }
226
+
227
+ function extent_of(markdown: string, chosen: Heading): number {
228
+ const lines = markdown.split('\n')
229
+
230
+ if (chosen.level === LABEL_LEVEL) return label_last_line(lines, chosen)
231
+
232
+ return last_line_of(headings(markdown), chosen, lines.length)
233
+ }
234
+
235
+ function section(markdown: string, wanted: string): Section | undefined {
236
+ const chosen = choose(markdown, wanted)
116
237
 
117
238
  if (chosen === undefined) return undefined
118
239
 
119
- const last_line = last_line_of(found, chosen, lines.length)
240
+ const last_line = extent_of(markdown, chosen)
241
+ const lines = markdown.split('\n').slice(chosen.line, last_line + SINGLE_MATCH)
120
242
 
121
243
  return {
122
244
  title: chosen.title,
123
245
  level: chosen.level,
124
246
  first_line: chosen.line,
125
247
  last_line,
126
- text: lines.slice(chosen.line, last_line + SINGLE_MATCH).join('\n'),
248
+ text: lines.join('\n'),
127
249
  }
128
250
  }
129
251
 
130
252
  // Ambiguity and absence are different answers, and the caller reports them differently: one names
131
253
  // the candidates, the other lists every heading the file has.
254
+ // Candidates follow `choose`'s order — headings, and labels only when no heading matches — so a
255
+ // refusal never suggests a label that citing it could not select.
132
256
  function candidates(markdown: string, wanted: string): Array<string> {
133
- return prefixed(headings(markdown), wanted).map((heading) => heading.title)
257
+ const near = prefixed(headings(markdown), wanted)
258
+ const listed = near.length > 0 ? near : prefixed(labels(markdown), wanted)
259
+
260
+ return listed.map((heading) => heading.title)
134
261
  }
135
262
 
136
263
  function read_optional(file_path: string): string | undefined {
@@ -70,7 +70,7 @@ const ENTRY_READ_BUDGET: ReadonlyArray<EntryBudget> = [
70
70
  // list, so its ~48KB read crossed a block downward and the recorded ceiling drops to the block multiple
71
71
  // the stale-ratchet message named. The other entries still read it, so their rows hold.
72
72
  // joshuafolkken/kit#2345 added the `implementation-unit` delegation row (full fan-out procedure in
73
- // `docs/josh-commands-automation.md` → "`josh fanout`"); `delegation.md` gained only a one-clause mention and a
73
+ // `docs/josh-commands-backlog.md` → "`josh fanout`"); `delegation.md` gained only a one-clause mention and a
74
74
  // pointer, folded into the existing enumeration and offset by tightening `delegation.md` prose, so every entry
75
75
  // stayed within its block (kickoff has no block of headroom — its pre-#2294 ceiling is the next
76
76
  // multiple, which the downward ratchet holds shut).
@@ -101,12 +101,14 @@ const ENTRY_READ_BUDGET: ReadonlyArray<EntryBudget> = [
101
101
  // provider prose moved to `docs/josh-commands-automation.md`, and the failure-only sections moved to
102
102
  // `backlogrun-recovery.md`, which the parent's reach still charges whole.
103
103
  // joshuafolkken/kit#3171 cut `CLAUDE.md` to triggers and pointers (~12 KB), lowering every entry.
104
- { entry: 'kickoff', bytes: 45_056 },
104
+ // joshuafolkken/kit#3256 moved `CLAUDE.md`'s residency prose to `residency.md`, lowering kickoff and
105
+ // halfrun one block each.
106
+ { entry: 'kickoff', bytes: 40_960 },
105
107
  // Raised in joshuafolkken/kit#3177: #3173, #3181 and #3183 merged in parallel and together tipped
106
108
  // fullrun 12 bytes over its block; none of #3177's documents are in its read set.
107
109
  // Lowered back in joshuafolkken/kit#3180: the history and lint-settled proofs left its read set.
108
110
  { entry: 'fullrun', bytes: 57_344 },
109
- { entry: 'halfrun', bytes: 57_344 },
111
+ { entry: 'halfrun', bytes: 53_248 },
110
112
  { entry: 'prrun', bytes: 61_440 },
111
113
  { entry: 'backlogrun', bytes: 176_128 },
112
114
  { entry: LANE_CHILD, bytes: 57_344 },
@@ -14,20 +14,20 @@
14
14
 
15
15
  import { existsSync } from 'node:fs'
16
16
  import path from 'node:path'
17
- import { cost_tokens } from '#scripts/cost-runtime/cost-tokens'
18
17
  import { PACKAGE_DIR } from '#scripts/init/init-paths'
19
18
  import { bash_output_cap_reader } from './bash-output-cap'
20
- import { document_section, type Section } from './document-section'
19
+ import { document_section } from './document-section'
20
+ import { read_set_cost, type Cost } from './read-set-cost'
21
21
  import { read_set_point_of_use } from './read-set-point-of-use'
22
22
 
23
23
  const { POINT_OF_USE_FILES, POINT_OF_USE_BY_ENTRY, is_point_of_use, point_of_use_files } =
24
24
  read_set_point_of_use
25
+ const { cost_of, scoped_file_cost, total } = read_set_cost
25
26
 
26
27
  const SKILL_DIRECTORY = path.join('.claude', 'skills', 'workflow-commands')
27
28
  const SKILL_FILE = 'SKILL.md'
28
29
  const TABLE_SECTION = '1. Which file to read'
29
30
  const NOTHING = 0
30
- const ONE_LINE = 1
31
31
 
32
32
  // The point-of-use classification — which documents an entry names but reads only at a later step —
33
33
  // is its own concern, split into `read-set-point-of-use.ts` in joshuafolkken/kit#2289. This file
@@ -54,11 +54,6 @@ interface SectionReference {
54
54
  heading: string
55
55
  }
56
56
 
57
- interface Cost {
58
- bytes: number
59
- tokens: number
60
- }
61
-
62
57
  interface SectionCost extends SectionReference {
63
58
  cost: Cost
64
59
  is_resolved: boolean
@@ -297,21 +292,6 @@ function read_set(root: string, entry: string): ReadSet {
297
292
  return { entry, files, sections: sections_for(root, entry, files) }
298
293
  }
299
294
 
300
- function cost_of(text: string): Cost {
301
- return { bytes: Buffer.byteLength(text, 'utf8'), tokens: cost_tokens.estimate(text) }
302
- }
303
-
304
- function total(costs: ReadonlyArray<Cost>): Cost {
305
- const running = { bytes: NOTHING, tokens: NOTHING }
306
-
307
- for (const cost of costs) {
308
- running.bytes += cost.bytes
309
- running.tokens += cost.tokens
310
- }
311
-
312
- return running
313
- }
314
-
315
295
  function section_cost(root: string, reference: SectionReference): SectionCost {
316
296
  const found = document_section.section(read_document(root, reference.file), reference.heading)
317
297
 
@@ -334,74 +314,13 @@ function headings_for(sections: ReadonlyArray<SectionReference>, file: string):
334
314
  return sections.filter((reference) => reference.file === file).map((one) => one.heading)
335
315
  }
336
316
 
337
- function add_range(charged: Set<number>, found: Section): void {
338
- for (let index = found.first_line; index <= found.last_line; index += ONE_LINE) charged.add(index)
339
- }
340
-
341
- // `undefined` the moment one heading does not resolve, because that reference is charged at its whole
342
- // file and there is then nothing to union.
343
- function charged_lines(markdown: string, headings: ReadonlyArray<string>): Set<number> | undefined {
344
- const charged = new Set<number>()
345
-
346
- for (const heading of headings) {
347
- const found = document_section.section(markdown, heading)
348
-
349
- if (found === undefined) return undefined
350
-
351
- add_range(charged, found)
352
- }
353
-
354
- return charged
355
- }
356
-
357
- // **The union's charged lines are split into contiguous runs, and each run is costed on its own
358
- // natural text.** Joining non-adjacent lines with `\n` into one string fabricates a token boundary at
359
- // every gap the union skipped, so the estimate could drift a token *above* the per-reference sum even
360
- // with no real overlap — a false negative saving the measurement must never print
361
- // (joshuafolkken/kit#1934). A run is a maximal stretch of consecutive charged lines, which is exactly
362
- // the contiguous text a reader of that section actually reads, so summing the runs both mirrors the
363
- // real read and keeps `scoped` at or below the per-reference sum by construction.
364
- function extend_or_start(runs: Array<Array<number>>, index: number): void {
365
- const current = runs.at(-1)
366
-
367
- if (current?.at(-1) === index - ONE_LINE) current.push(index)
368
- else runs.push([index])
369
- }
370
-
371
- function contiguous_runs(charged: ReadonlySet<number>): Array<Array<number>> {
372
- const runs: Array<Array<number>> = []
373
- const ascending = [...charged].toSorted((left, right) => left - right)
374
-
375
- for (const index of ascending) extend_or_start(runs, index)
376
-
377
- return runs
378
- }
379
-
380
- function run_text(lines: ReadonlyArray<string>, run: ReadonlyArray<number>): string {
381
- return run.map((index) => lines[index] ?? '').join('\n')
382
- }
383
-
384
- // **Charged per file over the union of the lines its references cover, never per reference.**
385
- // Summing the references instead double-counts the two ways one file can be cited twice — two
386
- // unresolved headings each charged at the whole file, and a `##` section cited beside one of its own
387
- // `###` children, which `section()` already returns inside the parent. Either one could push the
388
- // scoped figure above the whole one and print a *negative* saving, which is the single direction this
389
- // measurement must not be able to move (joshuafolkken/kit#1776 review round 1).
390
- function file_scoped_cost(root: string, file: string, headings: ReadonlyArray<string>): Cost {
391
- const markdown = read_document(root, file)
392
- const charged = charged_lines(markdown, headings)
393
-
394
- if (charged === undefined) return cost_of(markdown)
395
-
396
- const lines = markdown.split('\n')
397
-
398
- return total(contiguous_runs(charged).map((run) => cost_of(run_text(lines, run))))
399
- }
400
-
317
+ // The per-file union that keeps `scoped` at or below `whole` lives in `read-set-cost.ts`.
401
318
  function scoped_cost(root: string, sections: ReadonlyArray<SectionReference>): Cost {
402
319
  const files = unique(sections.map((reference) => reference.file))
403
320
 
404
- return total(files.map((file) => file_scoped_cost(root, file, headings_for(sections, file))))
321
+ return total(
322
+ files.map((file) => scoped_file_cost(read_document(root, file), headings_for(sections, file))),
323
+ )
405
324
  }
406
325
 
407
326
  function file_costs(root: string, names: ReadonlyArray<string>): Array<FileCost> {
@@ -491,5 +410,7 @@ const entry_read_set = {
491
410
  total,
492
411
  }
493
412
 
494
- export type { Cost, FileCost, ReadSet, ReadSetCost, SectionCost, SectionReference }
413
+ export type { FileCost, ReadSet, ReadSetCost, SectionCost, SectionReference }
495
414
  export { entry_read_set }
415
+
416
+ export { type Cost } from './read-set-cost'
@@ -0,0 +1,98 @@
1
+ import { cost_tokens } from '#scripts/cost-runtime/cost-tokens'
2
+ import { document_section, type Section } from './document-section'
3
+
4
+ // What a read costs, and the scoped union that charges one file's cited sections once — split out
5
+ // of `entry-read-set.ts` when it reached its line limit (joshuafolkken/kit#3264). `entry_read_set`
6
+ // re-exports `cost_of`, `total` and `Cost` under the names they always had, so the move changed no
7
+ // call site.
8
+
9
+ const NOTHING = 0
10
+ const ONE_LINE = 1
11
+
12
+ interface Cost {
13
+ bytes: number
14
+ tokens: number
15
+ }
16
+
17
+ function cost_of(text: string): Cost {
18
+ return { bytes: Buffer.byteLength(text, 'utf8'), tokens: cost_tokens.estimate(text) }
19
+ }
20
+
21
+ function total(costs: ReadonlyArray<Cost>): Cost {
22
+ const running = { bytes: NOTHING, tokens: NOTHING }
23
+
24
+ for (const cost of costs) {
25
+ running.bytes += cost.bytes
26
+ running.tokens += cost.tokens
27
+ }
28
+
29
+ return running
30
+ }
31
+
32
+ function add_range(charged: Set<number>, found: Section): void {
33
+ for (let index = found.first_line; index <= found.last_line; index += ONE_LINE) charged.add(index)
34
+ }
35
+
36
+ // `undefined` the moment one heading does not resolve, because that reference is charged at its whole
37
+ // file and there is then nothing to union.
38
+ function charged_lines(markdown: string, headings: ReadonlyArray<string>): Set<number> | undefined {
39
+ const charged = new Set<number>()
40
+
41
+ for (const heading of headings) {
42
+ const found = document_section.section(markdown, heading)
43
+
44
+ if (found === undefined) return undefined
45
+
46
+ add_range(charged, found)
47
+ }
48
+
49
+ return charged
50
+ }
51
+
52
+ // **The union's charged lines are split into contiguous runs, and each run is costed on its own
53
+ // natural text.** Joining non-adjacent lines with `\n` into one string fabricates a token boundary at
54
+ // every gap the union skipped, so the estimate could drift a token *above* the per-reference sum even
55
+ // with no real overlap — a false negative saving the measurement must never print
56
+ // (joshuafolkken/kit#1934). A run is a maximal stretch of consecutive charged lines, which is exactly
57
+ // the contiguous text a reader of that section actually reads, so summing the runs both mirrors the
58
+ // real read and keeps `scoped` at or below the per-reference sum by construction.
59
+ function extend_or_start(runs: Array<Array<number>>, index: number): void {
60
+ const current = runs.at(-1)
61
+
62
+ if (current?.at(-1) === index - ONE_LINE) current.push(index)
63
+ else runs.push([index])
64
+ }
65
+
66
+ function contiguous_runs(charged: ReadonlySet<number>): Array<Array<number>> {
67
+ const runs: Array<Array<number>> = []
68
+ const ascending = [...charged].toSorted((left, right) => left - right)
69
+
70
+ for (const index of ascending) extend_or_start(runs, index)
71
+
72
+ return runs
73
+ }
74
+
75
+ function run_text(lines: ReadonlyArray<string>, run: ReadonlyArray<number>): string {
76
+ return run.map((index) => lines[index] ?? '').join('\n')
77
+ }
78
+
79
+ // **Charged per file over the union of the lines its references cover, never per reference.**
80
+ // Summing the references instead double-counts the two ways one file can be cited twice — two
81
+ // unresolved headings each charged at the whole file, and a `##` section cited beside one of its own
82
+ // `###` children, which `section()` already returns inside the parent. Either one could push the
83
+ // scoped figure above the whole one and print a *negative* saving, which is the single direction this
84
+ // measurement must not be able to move (joshuafolkken/kit#1776 review round 1).
85
+ function scoped_file_cost(markdown: string, headings: ReadonlyArray<string>): Cost {
86
+ const charged = charged_lines(markdown, headings)
87
+
88
+ if (charged === undefined) return cost_of(markdown)
89
+
90
+ const lines = markdown.split('\n')
91
+
92
+ return total(contiguous_runs(charged).map((run) => cost_of(run_text(lines, run))))
93
+ }
94
+
95
+ const read_set_cost = { cost_of, scoped_file_cost, total }
96
+
97
+ export type { Cost }
98
+ export { read_set_cost }
@@ -147,6 +147,7 @@ function splice_declaration(
147
147
  if (index === input.first) return [...input.rendered]
148
148
  if (input.dropped.has(index)) return []
149
149
 
150
+ // eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
150
151
  return [line]
151
152
  })
152
153
  }
@@ -301,7 +301,7 @@ function reported_references(
301
301
  .flatMap((reference) => {
302
302
  const state = reported_state(states, reference)
303
303
 
304
- return state === undefined ? [] : [{ reference, state }]
304
+ return state === undefined ? [] : { reference, state }
305
305
  })
306
306
  }
307
307
 
@@ -73,9 +73,10 @@ function order_pairs(
73
73
  const blocked = find_child(children, { repo: epic_repo, number: link.blocked })
74
74
  if (blocker === undefined || blocked === undefined) return []
75
75
 
76
- return is_open_local(blocker, current_repo) && is_open_local(blocked, current_repo)
77
- ? [{ blocker, blocked }]
78
- : []
76
+ if (!is_open_local(blocker, current_repo) || !is_open_local(blocked, current_repo)) return []
77
+
78
+ // eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
79
+ return [{ blocker, blocked }]
79
80
  })
80
81
  }
81
82
 
@@ -4,6 +4,7 @@ import { git_gh_command } from '#scripts/gh/git-gh-command'
4
4
  import { cutoff_of, type ScanCutoff } from '#scripts/git/listing-cutoff'
5
5
  import { parse_json_array_or_undefined } from '#scripts/git/parse-json-array'
6
6
  import { bounded_pool } from '#scripts/lib/bounded-pool'
7
+ import type { PollOptions } from '#scripts/lib/poll'
7
8
  import { issue_citation } from '#scripts/rules/issue-citation'
8
9
  import { z } from 'zod'
9
10
  import {
@@ -14,6 +15,7 @@ import {
14
15
  } from './epic-bundle'
15
16
  import { epic_bundle_evidence } from './epic-bundle-evidence'
16
17
  import { epic_bundle_gaps } from './epic-bundle-gaps'
18
+ import { epic_bundle_poll } from './epic-bundle-poll'
17
19
  import { epic_bundle_referenced, type ReferencedContext } from './epic-bundle-referenced'
18
20
  import { epic_index, epic_schema, type FetchedEpics } from './epic-index'
19
21
  import { epic_issue } from './epic-issue'
@@ -393,9 +395,18 @@ async function report_widened(
393
395
  return report_decision(subject, widened, repo)
394
396
  }
395
397
 
396
- // The recommendation for one issue, from the open backlog around it.
397
- async function report_for(issue_number: number, repo: string): Promise<number> {
398
- const backlog = await read_backlog(repo)
398
+ // The recommendation for one issue, from the open backlog around it. `options` is the poll a caller
399
+ // holding a just-filed issue passes, since the listing trails the create call (`epic-bundle-poll.ts`).
400
+ async function report_for(
401
+ issue_number: number,
402
+ repo: string,
403
+ options?: PollOptions,
404
+ ): Promise<number> {
405
+ const backlog = await epic_bundle_poll.read_until_listed(
406
+ async () => await read_backlog(repo),
407
+ issue_number,
408
+ options,
409
+ )
399
410
 
400
411
  if (!backlog.is_readable) {
401
412
  console.error(unreadable_backlog_message(backlog))
@@ -0,0 +1,41 @@
1
+ import { poll, type PollOptions } from '#scripts/lib/poll'
2
+
3
+ // **The open listing trails the create call** (joshuafolkken/kit#3332): `issue:file` asks
4
+ // `epic:bundle` about the issue it has just filed, and the list endpoint had not shown it yet on every
5
+ // filing of one run, so the placement was skipped each time. A caller that knows the issue is fresh
6
+ // passes a poll and the listing is read again until it shows the issue. An unreadable listing is
7
+ // final, since reading it again is not what would change it.
8
+
9
+ // A number typed at the command line names an issue that exists already, so it is read once.
10
+ const SINGLE_READ: PollOptions = { attempts: 1, interval_ms: 0 }
11
+
12
+ interface Listing {
13
+ is_readable: boolean
14
+ issues: ReadonlyArray<{ number: number }>
15
+ }
16
+
17
+ function is_settled(listing: Listing, issue_number: number): boolean {
18
+ return !listing.is_readable || listing.issues.some((issue) => issue.number === issue_number)
19
+ }
20
+
21
+ async function read_until_listed<T extends Listing>(
22
+ read: () => Promise<T>,
23
+ issue_number: number,
24
+ options: PollOptions = SINGLE_READ,
25
+ ): Promise<T> {
26
+ const reads: Array<T> = []
27
+
28
+ await poll.poll_until(async () => {
29
+ const listing = await read()
30
+
31
+ reads.push(listing)
32
+
33
+ return is_settled(listing, issue_number)
34
+ }, options)
35
+
36
+ return reads.at(-1) ?? (await read())
37
+ }
38
+
39
+ const epic_bundle_poll = { read_until_listed }
40
+
41
+ export { epic_bundle_poll }
@@ -1,6 +1,6 @@
1
1
  import { git_gh_command } from '#scripts/gh/git-gh-command'
2
+ import { open_issue_schema, type OpenIssueData } from '#scripts/git/git-schemas'
2
3
  import { read_json_listing } from '#scripts/git/parse-json-array'
3
- import { open_issue_schema, type OpenIssueData } from '#scripts/git/schemas'
4
4
  import {
5
5
  ALREADY_DONE_LABEL,
6
6
  has_any_label,
@@ -70,11 +70,12 @@ function is_same_blockers(
70
70
  left: ReadonlyArray<IssueReference>,
71
71
  right: ReadonlyArray<IssueReference>,
72
72
  ): boolean {
73
- const sorted = (blockers: ReadonlyArray<IssueReference>): string =>
74
- blockers
73
+ function sorted(blockers: ReadonlyArray<IssueReference>): string {
74
+ return blockers
75
75
  .map((blocker) => epic_graph.key_of(blocker))
76
76
  .toSorted((first, second) => first.localeCompare(second))
77
77
  .join(',')
78
+ }
78
79
 
79
80
  return sorted(left) === sorted(right)
80
81
  }
@@ -217,7 +217,7 @@ function is_ready(node: string, context: ClassifyContext): boolean {
217
217
  function fill_memo(children: ReadonlyArray<EpicChild>, context: ClassifyContext): void {
218
218
  const pending = new Set(children.map((child) => epic_graph.key_of(child)))
219
219
 
220
- const peel = (): number => {
220
+ function peel(): number {
221
221
  const ready = [...pending].filter((node) => is_ready(node, context))
222
222
 
223
223
  for (const node of ready) {