@joshuafolkken/kit 1.1178.0 → 1.1229.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 (425) 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 +6 -2
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +23 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -2
  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 +5 -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 +1 -1
  20. package/.claude/skills/workflow-commands/observation-ledger.md +3 -3
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +6 -1
  22. package/.claude/skills/workflow-commands/prerequisite.md +2 -2
  23. package/.claude/skills/workflow-commands/progress-watcher.md +1 -1
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +1 -1
  25. package/.codex/hooks.json +2 -2
  26. package/.cursorrules +6 -41
  27. package/.github/actions/setup-node/action.yml +1 -1
  28. package/.github/workflows/auto-tag.yml +3 -4
  29. package/.github/workflows/ci.yml +32 -31
  30. package/.github/workflows/dependabot-auto-merge.yml +13 -6
  31. package/.github/workflows/publish.yml +48 -4
  32. package/.github/workflows/sonar-qube.yml +3 -0
  33. package/.pnpmfile.mjs +4 -3
  34. package/.prettierignore +8 -2
  35. package/CLAUDE.md +18 -20
  36. package/README.md +26 -19
  37. package/SECURITY.md +15 -1
  38. package/dist/CLAUDE.md +18 -20
  39. package/dist/commands/document-section-cli.js +734 -0
  40. package/dist/hooks/audit-provision.js +346 -0
  41. package/dist/hooks/batch-guard.js +5 -4
  42. package/dist/hooks/{chunk-S6X6WYKR.js → chunk-55HHG5PX.js} +1210 -711
  43. package/dist/hooks/{chunk-ZAL3NRGL.js → chunk-5VMKCCC4.js} +25 -10
  44. package/dist/hooks/chunk-5VNDSNG3.js +12 -0
  45. package/dist/hooks/{chunk-32GQTL4F.js → chunk-6HJJXRBX.js} +4 -2
  46. package/dist/hooks/chunk-CDY4NFQG.js +15 -0
  47. package/dist/hooks/chunk-ELCN5LCH.js +135 -0
  48. package/dist/hooks/{chunk-VESKRQOF.js → chunk-F56MFS4B.js} +3 -3
  49. package/dist/hooks/{chunk-UAGLLIGZ.js → chunk-FIVDHQGT.js} +7 -6
  50. package/dist/hooks/{chunk-3V37IID6.js → chunk-JBBMAPC4.js} +23 -14
  51. package/dist/hooks/{chunk-G6KRCKEC.js → chunk-JUDKDCDP.js} +496 -378
  52. package/dist/hooks/{chunk-GDS5N2KB.js → chunk-NV2JRZZO.js} +718 -681
  53. package/dist/hooks/{chunk-VQLLNFNT.js → chunk-RNWHN5BP.js} +3 -3
  54. package/dist/hooks/{chunk-DO6NWWHU.js → chunk-TQNPDZNH.js} +4 -4
  55. package/dist/hooks/{chunk-I4XSL4TT.js → chunk-VC3PZPZO.js} +1 -1
  56. package/dist/hooks/codex-hook-adapter.js +14 -12
  57. package/dist/hooks/duplicate-read-guard.js +6 -5
  58. package/dist/hooks/format-edited.js +4 -3
  59. package/dist/hooks/inputs.json +217 -195
  60. package/dist/hooks/investigation-guard.js +6 -5
  61. package/dist/hooks/pretool-guard.js +12 -10
  62. package/dist/hooks/session-lang.js +2 -4
  63. package/dist/hooks/stop-guard.js +66 -58
  64. package/dist/josh.js +868 -74
  65. package/dist/version/index.d.ts +7 -2
  66. package/dist/version/index.js +152 -75
  67. package/eslint/base.js +23 -1
  68. package/eslint/rules/code-quality.js +3 -0
  69. package/eslint/rules/early-return-one-liner.js +98 -0
  70. package/eslint/rules/unicorn.js +16 -1
  71. package/package.json +7 -6
  72. package/pnpm-workspace.yaml +9 -4
  73. package/prettier/basic.d.ts +3 -1
  74. package/prettier/index.d.ts +3 -1
  75. package/prettier/static.d.ts +3 -1
  76. package/prompts/coding-standards.md +1 -1
  77. package/prompts/collaboration-workflow/file-edits.md +4 -4
  78. package/prompts/collaboration-workflow/glossary.md +1 -1
  79. package/prompts/collaboration-workflow/operating-rules.md +17 -9
  80. package/prompts/collaboration-workflow/overview.md +2 -2
  81. package/prompts/collaboration-workflow/principles.md +14 -12
  82. package/prompts/collaboration-workflow/report-format.md +1 -1
  83. package/prompts/collaboration-workflow/residency.md +1 -1
  84. package/prompts/collaboration-workflow/rule-delivery.md +6 -6
  85. package/prompts/collaboration-workflow/shell-body.md +6 -6
  86. package/prompts/collaboration-workflow/turn-batching.md +1 -1
  87. package/prompts/collaboration-workflow/upstream-interrupt.md +12 -2
  88. package/prompts/collaboration-workflow/wip-cap.md +10 -10
  89. package/prompts/review-rubric.md +1 -2
  90. package/prompts/review.md +1 -1
  91. package/scripts/agent/agent-headless.ts +29 -0
  92. package/scripts/agent/agent-launch-environment.ts +6 -2
  93. package/scripts/agent/codex-agent-argv.ts +2 -1
  94. package/scripts/auto-ok/auto-ok-cli.ts +1 -1
  95. package/scripts/backlog/backlog-arrival.ts +3 -3
  96. package/scripts/backlog/backlog-budget.ts +1 -1
  97. package/scripts/backlog/backlog-drive-args.ts +135 -0
  98. package/scripts/backlog/backlog-drive-cli.ts +11 -137
  99. package/scripts/backlog/backlog-drive-launch.ts +39 -0
  100. package/scripts/backlog/backlog-drive-named-offer.ts +3 -5
  101. package/scripts/backlog/backlog-drive-named.ts +3 -3
  102. package/scripts/backlog/backlog-drive-offer-argv.ts +1 -1
  103. package/scripts/backlog/backlog-drive-owner.ts +1 -1
  104. package/scripts/backlog/backlog-drive-restore.ts +1 -1
  105. package/scripts/backlog/backlog-drive-retrospective.ts +1 -1
  106. package/scripts/backlog/backlog-drive.ts +1 -1
  107. package/scripts/backlog/backlog-next.ts +16 -5
  108. package/scripts/backlog/backlog-offer-cli.ts +6 -6
  109. package/scripts/backlog/backlog-offer.ts +1 -1
  110. package/scripts/backlog/backlog-plan-cli.ts +1 -1
  111. package/scripts/backlog/backlog-pool.ts +57 -11
  112. package/scripts/backlog/backlog-ready.ts +1 -1
  113. package/scripts/backlog/backlog-scope.ts +1 -1
  114. package/scripts/backlog/backlog-stalled-detect.ts +2 -2
  115. package/scripts/backlog/backlog-stalled.ts +1 -1
  116. package/scripts/build/build-codex-hooks.ts +3 -1
  117. package/scripts/build/build-commands.ts +66 -0
  118. package/scripts/build/build-hooks.ts +2 -1
  119. package/scripts/build/build-library.ts +12 -3
  120. package/scripts/build/build.ts +70 -0
  121. package/scripts/build/local-bin.ts +3 -13
  122. package/scripts/delegation/investigation-reads.ts +3 -1
  123. package/scripts/discovery/repo-origin.ts +5 -5
  124. package/scripts/doctor/doctor-consumer.ts +2 -2
  125. package/scripts/doctor/doctor-io.ts +2 -3
  126. package/scripts/document/capped-print-part.ts +30 -0
  127. package/scripts/document/capped-print.ts +3 -4
  128. package/scripts/document/document-byte-budget.ts +11 -2
  129. package/scripts/document/document-scan.ts +117 -0
  130. package/scripts/document/document-section.ts +143 -16
  131. package/scripts/document/entry-read-budget.ts +5 -3
  132. package/scripts/document/entry-read-set.ts +10 -89
  133. package/scripts/document/read-set-cost.ts +98 -0
  134. package/scripts/epic/epic-add-body.ts +1 -0
  135. package/scripts/epic/epic-audit-checks.ts +1 -1
  136. package/scripts/epic/epic-audit-rationale.ts +4 -3
  137. package/scripts/epic/epic-bundle-cli.ts +14 -3
  138. package/scripts/epic/epic-bundle-poll.ts +41 -0
  139. package/scripts/epic/epic-busy.ts +1 -1
  140. package/scripts/epic/epic-candidate-confirm.ts +3 -2
  141. package/scripts/epic/epic-classify.ts +1 -1
  142. package/scripts/epic/epic-cli-argv.ts +78 -46
  143. package/scripts/epic/epic-cli-remove.ts +11 -19
  144. package/scripts/epic/epic-cli.ts +74 -73
  145. package/scripts/epic/epic-close-comment.ts +1 -1
  146. package/scripts/epic/epic-close.ts +1 -1
  147. package/scripts/epic/epic-cross-repo.ts +5 -5
  148. package/scripts/epic/epic-graph.ts +1 -1
  149. package/scripts/epic/epic-index.ts +1 -1
  150. package/scripts/epic/epic-issue.ts +2 -6
  151. package/scripts/epic/epic-lane-offer.ts +2 -2
  152. package/scripts/epic/epic-next.ts +29 -42
  153. package/scripts/epic/epic-solo-stale.ts +1 -1
  154. package/scripts/epic/epic-triage.ts +8 -4
  155. package/scripts/epic/epic-validate.ts +1 -1
  156. package/scripts/followup/git-followup-finish.ts +2 -2
  157. package/scripts/followup/git-followup-label.ts +2 -2
  158. package/scripts/followup/git-followup-merged.ts +2 -2
  159. package/scripts/followup/git-pr-followup-checks.ts +142 -0
  160. package/scripts/followup/git-pr-followup.ts +4 -129
  161. package/scripts/gate/core-budget.ts +69 -1
  162. package/scripts/gate/gate-plan.ts +47 -7
  163. package/scripts/gate/gate-skip.ts +38 -7
  164. package/scripts/gate/hook-gate-reuse.ts +12 -10
  165. package/scripts/gate/pre-commit-type-check.ts +4 -1
  166. package/scripts/gate/project-checks.ts +6 -9
  167. package/scripts/gate/type-check-step.ts +2 -3
  168. package/scripts/gate/verification-gate.ts +27 -8
  169. package/scripts/gh/git-gh-api-path.ts +2 -2
  170. package/scripts/gh/git-gh-issue-rest.ts +3 -3
  171. package/scripts/gh/git-gh-issue-write.ts +53 -7
  172. package/scripts/gh/git-gh-pr-read.ts +64 -19
  173. package/scripts/gh/git-gh-pr-rest.ts +1 -1
  174. package/scripts/gh/git-gh-pr.ts +5 -1
  175. package/scripts/gh/git-pr-ai-review.ts +1 -1
  176. package/scripts/gh/git-pr-checks-eval.ts +1 -3
  177. package/scripts/gh/git-pr-checks-parse.ts +1 -1
  178. package/scripts/gh/git-pr-checks-watch.ts +6 -5
  179. package/scripts/gh/git-pr-checks.ts +7 -5
  180. package/scripts/gh/git-pr-coderabbit.ts +1 -1
  181. package/scripts/gh/git-pr-error.ts +1 -3
  182. package/scripts/gh/git-pr.ts +9 -13
  183. package/scripts/gh/github-issue-url.ts +2 -2
  184. package/scripts/git/constants.ts +2 -6
  185. package/scripts/git/git-command.ts +20 -218
  186. package/scripts/git/git-diff-reads.ts +225 -0
  187. package/scripts/git/git-error.ts +4 -12
  188. package/scripts/git/git-pre-push-hook.ts +139 -0
  189. package/scripts/git/git-prompt.ts +2 -6
  190. package/scripts/git/git-spawn-sync.ts +3 -1
  191. package/scripts/git/git-spawn.ts +1 -1
  192. package/scripts/git/git-staging.ts +4 -6
  193. package/scripts/git/git-status.ts +3 -9
  194. package/scripts/hooks/codex-hook-adapter.ts +1 -1
  195. package/scripts/hooks/edited-cspell.ts +2 -4
  196. package/scripts/hooks/format-edited-file.ts +3 -12
  197. package/scripts/hooks/pre-push-unit.ts +31 -9
  198. package/scripts/hooks/reserved-run.ts +2 -2
  199. package/scripts/hooks/run-hook.sh +5 -4
  200. package/scripts/hooks/stop-guard.ts +5 -3
  201. package/scripts/init/init-actions.ts +0 -5
  202. package/scripts/init/init-ai-copy.ts +13 -2
  203. package/scripts/init/init-logic-json-merge.ts +19 -0
  204. package/scripts/init/init-logic-secretlint.ts +7 -7
  205. package/scripts/init/init-logic-workspace.ts +41 -2
  206. package/scripts/init/init-logic.ts +16 -26
  207. package/scripts/init/init.ts +5 -15
  208. package/scripts/init/initial-commit.ts +4 -4
  209. package/scripts/init/kit-development-versions.ts +40 -0
  210. package/scripts/init/kit-setup-state.ts +2 -2
  211. package/scripts/init/start-exec.ts +68 -19
  212. package/scripts/init/start-setup-pr.ts +9 -9
  213. package/scripts/init/start-steps.ts +6 -10
  214. package/scripts/issue/defect-rate-cli.ts +1 -1
  215. package/scripts/issue/git-next-issues.ts +1 -1
  216. package/scripts/issue/issue-auto-ok.ts +1 -1
  217. package/scripts/issue/issue-backlinks-cli.ts +2 -2
  218. package/scripts/issue/issue-backlinks.ts +9 -9
  219. package/scripts/issue/issue-comment-cli.ts +19 -13
  220. package/scripts/issue/issue-file-cli.ts +45 -18
  221. package/scripts/issue/issue-file.ts +24 -1
  222. package/scripts/issue/issue-scout-closed.ts +1 -1
  223. package/scripts/issue/issue-state-cli.ts +30 -74
  224. package/scripts/josh/josh-command-map.ts +2 -0
  225. package/scripts/josh/josh-command-types.ts +11 -0
  226. package/scripts/josh/josh-commands-ai.ts +4 -4
  227. package/scripts/josh/josh-commands-backlog.ts +4 -1
  228. package/scripts/josh/josh-commands-development.ts +10 -0
  229. package/scripts/josh/josh-commands-document.ts +1 -0
  230. package/scripts/josh/josh-commands-lane.ts +22 -0
  231. package/scripts/josh/josh-commands-maintenance.ts +4 -4
  232. package/scripts/josh/josh-commands-run.ts +12 -7
  233. package/scripts/josh/josh-in-process.ts +28 -9
  234. package/scripts/josh/josh-logic.ts +33 -15
  235. package/scripts/josh/process-identity.ts +1 -3
  236. package/scripts/josh/session-language-cli.ts +3 -4
  237. package/scripts/lane/lane-await.ts +5 -1
  238. package/scripts/lane/lane-child-invocation.ts +2 -2
  239. package/scripts/lane/lane-cli.ts +3 -3
  240. package/scripts/lane/lane-dispatch.ts +14 -10
  241. package/scripts/lane/lane-environment.ts +5 -1
  242. package/scripts/lane/lane-handoff.ts +1 -1
  243. package/scripts/lane/lane-install.ts +2 -13
  244. package/scripts/lane/lane-launch-cli.ts +15 -1
  245. package/scripts/lane/lane-ledger.ts +145 -0
  246. package/scripts/lane/lane-leftover.ts +1 -1
  247. package/scripts/lane/lane-load.ts +108 -0
  248. package/scripts/lane/lane-measure-cli.ts +149 -0
  249. package/scripts/lane/lane-occupancy.ts +3 -0
  250. package/scripts/lane/lane-reap.ts +1 -3
  251. package/scripts/lane/lane-relaunch.ts +1 -1
  252. package/scripts/lane/lane-stats.ts +224 -0
  253. package/scripts/lane/lane-vacant.ts +36 -0
  254. package/scripts/lane/openai-lane-ship.ts +2 -2
  255. package/scripts/lane/openai-lane-supervisor-owner.ts +180 -0
  256. package/scripts/lane/openai-lane-supervisor.ts +7 -165
  257. package/scripts/lane/openai-review-broker.ts +2 -4
  258. package/scripts/lib/ancestor-directories.ts +35 -0
  259. package/scripts/lib/animation-helpers.ts +2 -2
  260. package/scripts/lib/buffered-process.ts +3 -7
  261. package/scripts/lib/cli-flags.ts +71 -1
  262. package/scripts/lib/spawn-exit.ts +2 -2
  263. package/scripts/lib/timeouts.ts +35 -2
  264. package/scripts/lines/line-budget.ts +4 -6
  265. package/scripts/notify/telegram-test.ts +6 -0
  266. package/scripts/observations/observation-ledger-line.ts +6 -6
  267. package/scripts/observations/observation-ledger-migrate.ts +55 -4
  268. package/scripts/observations/observation-ledger.ts +26 -7
  269. package/scripts/overrides/lockfile-overrides.ts +1 -1
  270. package/scripts/overrides/overrides-logic.ts +1 -1
  271. package/scripts/propagate/propagate-git.ts +1 -1
  272. package/scripts/propagate/propagate-publish.ts +2 -2
  273. package/scripts/propagate/propagate-steps.ts +20 -15
  274. package/scripts/propagate/propagate.ts +36 -27
  275. package/scripts/refactor/import-graph.ts +18 -1
  276. package/scripts/refactor/refactor-lint.ts +4 -4
  277. package/scripts/repo/repository-labels.ts +2 -2
  278. package/scripts/repo/required-checks-logic.ts +1 -3
  279. package/scripts/review/live-evidence.ts +2 -4
  280. package/scripts/review/review-brief-cli.ts +1 -1
  281. package/scripts/review/review-finding-ledger.ts +1 -1
  282. package/scripts/review/review-findings-cli.ts +1 -3
  283. package/scripts/rules/decision-oracle-batch.ts +128 -0
  284. package/scripts/rules/decision-oracle-tokens.ts +21 -0
  285. package/scripts/rules/decision-oracle.ts +8 -120
  286. package/scripts/rules/delivered-rules.ts +26 -182
  287. package/scripts/rules/direct-filing.ts +1 -1
  288. package/scripts/rules/early-heartbeat.ts +3 -3
  289. package/scripts/rules/git-argv.ts +1 -1
  290. package/scripts/rules/git-force.ts +1 -1
  291. package/scripts/rules/implementation-cut.ts +2 -2
  292. package/scripts/rules/index-guard.ts +1 -1
  293. package/scripts/rules/issue-comments.ts +118 -0
  294. package/scripts/rules/{issue-fold.ts → issue-fold-rule.ts} +2 -2
  295. package/scripts/rules/lane-background.ts +2 -2
  296. package/scripts/rules/lane-park.ts +18 -0
  297. package/scripts/rules/lane-split-park.ts +128 -0
  298. package/scripts/rules/oracle-firing.ts +1 -1
  299. package/scripts/rules/poll-loop.ts +1 -1
  300. package/scripts/rules/pre-gate-cut.ts +1 -1
  301. package/scripts/rules/protected-files.ts +59 -4
  302. package/scripts/rules/{run-tail.ts → run-tail-rule.ts} +2 -2
  303. package/scripts/rules/shell-body-trigger.ts +1 -1
  304. package/scripts/rules/stop-rules.ts +18 -7
  305. package/scripts/rules/test-declared-commit.ts +4 -4
  306. package/scripts/rules/wip-cap.ts +63 -0
  307. package/scripts/run/{run-carry-args.ts → carry/run-carry-args.ts} +37 -13
  308. package/scripts/run/{run-carry-change.ts → carry/run-carry-change.ts} +29 -7
  309. package/scripts/run/{run-carry-cli.ts → carry/run-carry-cli.ts} +4 -6
  310. package/scripts/run/{run-carry.ts → carry/run-carry.ts} +1 -1
  311. package/scripts/run/{run-cut-args.ts → cut/run-cut-args.ts} +1 -1
  312. package/scripts/run/{run-cut-cli.ts → cut/run-cut-cli.ts} +2 -2
  313. package/scripts/run/{run-cut.ts → cut/run-cut.ts} +1 -1
  314. package/scripts/run/{run-entry-cli.ts → entry/run-entry-cli.ts} +11 -11
  315. package/scripts/run/{run-entry.ts → entry/run-entry.ts} +2 -2
  316. package/scripts/run/{run-event-scope.ts → event/run-event-scope.ts} +1 -1
  317. package/scripts/run/{run-event-stream-emit.ts → event/run-event-stream-emit.ts} +1 -1
  318. package/scripts/run/{run-event-stream.ts → event/run-event-stream.ts} +16 -2
  319. package/scripts/run/{run-hold-cli.ts → hold/run-hold-cli.ts} +5 -5
  320. package/scripts/run/{run-merge-cli.ts → merge/run-merge-cli.ts} +10 -5
  321. package/scripts/run/{run-merge-steps.ts → merge/run-merge-steps.ts} +53 -5
  322. package/scripts/run/{run-merge.ts → merge/run-merge.ts} +1 -1
  323. package/scripts/run/{run-progress-cli.ts → progress/run-progress-cli.ts} +1 -1
  324. package/scripts/run/{run-progress-config.ts → progress/run-progress-config.ts} +2 -17
  325. package/scripts/run/{run-progress-read.ts → progress/run-progress-read.ts} +5 -5
  326. package/scripts/run/{run-progress.ts → progress/run-progress.ts} +1 -1
  327. package/scripts/run/{run-review-steps.ts → review/run-review-steps.ts} +2 -3
  328. package/scripts/run/run-ending.ts +2 -2
  329. package/scripts/run/run-halfrun-resume.ts +6 -1
  330. package/scripts/run/run-headless.ts +1 -1
  331. package/scripts/run/run-invocation.ts +85 -84
  332. package/scripts/run/run-liveness.ts +1 -1
  333. package/scripts/run/run-parent-cut-hook.ts +1 -1
  334. package/scripts/run/run-preflight.ts +6 -6
  335. package/scripts/run/run-prep-cli.ts +1 -1
  336. package/scripts/run/run-prrun-resume.ts +1 -1
  337. package/scripts/run/run-report-cli.ts +4 -4
  338. package/scripts/run/run-report.ts +2 -2
  339. package/scripts/run/run-status-cli.ts +1 -1
  340. package/scripts/run/run-step-cli.ts +3 -3
  341. package/scripts/run/run-step.ts +2 -2
  342. package/scripts/run/run-stop-notify.ts +1 -1
  343. package/scripts/run/run-stranded-detect.ts +4 -4
  344. package/scripts/run/run-stranded.ts +2 -2
  345. package/scripts/run/run-watcher-guard-cli.ts +1 -1
  346. package/scripts/run/run-watcher-guard.ts +5 -3
  347. package/scripts/run/run-watcher-hook.ts +1 -1
  348. package/scripts/run/{run-ship-cli.ts → ship/run-ship-cli.ts} +2 -2
  349. package/scripts/run/{run-ship-detach.ts → ship/run-ship-detach.ts} +3 -3
  350. package/scripts/run/ship/run-ship-lock.ts +50 -0
  351. package/scripts/run/{run-ship-preflight.ts → ship/run-ship-preflight.ts} +6 -2
  352. package/scripts/run/{run-ship-probe.ts → ship/run-ship-probe.ts} +2 -2
  353. package/scripts/run/{run-ship-return.ts → ship/run-ship-return.ts} +4 -3
  354. package/scripts/run/{run-ship-review-steps.ts → ship/run-ship-review-steps.ts} +3 -3
  355. package/scripts/run/{run-ship-scoped.ts → ship/run-ship-scoped.ts} +10 -1
  356. package/scripts/run/{run-ship-steps.ts → ship/run-ship-steps.ts} +11 -12
  357. package/scripts/run/ship/run-ship-stop-text.ts +13 -0
  358. package/scripts/run/{run-ship-sync.ts → ship/run-ship-sync.ts} +25 -11
  359. package/scripts/run/{run-tidy-lanes.ts → tidy/run-tidy-lanes.ts} +1 -1
  360. package/scripts/run/{run-wake-cli.ts → wake/run-wake-cli.ts} +23 -8
  361. package/scripts/run/{run-wake-describe.ts → wake/run-wake-describe.ts} +3 -3
  362. package/scripts/run/{run-wake-driver.ts → wake/run-wake-driver.ts} +59 -8
  363. package/scripts/run/{run-wake-handoff.ts → wake/run-wake-handoff.ts} +1 -1
  364. package/scripts/run/{run-wake-loop.ts → wake/run-wake-loop.ts} +8 -2
  365. package/scripts/run/wake/run-wake-restart.ts +58 -0
  366. package/scripts/run/{run-wake-session.ts → wake/run-wake-session.ts} +3 -3
  367. package/scripts/run/{run-wake.ts → wake/run-wake.ts} +1 -1
  368. package/scripts/safe-chain/preinstall-command.ts +20 -21
  369. package/scripts/safe-chain/project-config.ts +19 -15
  370. package/scripts/sync/sync-ai-files.ts +1 -1
  371. package/scripts/sync/sync-configs.ts +2 -1
  372. package/scripts/sync/sync.ts +15 -2
  373. package/scripts/test/unit-worker-share.ts +1 -0
  374. package/scripts/time-runtime/time-background.ts +5 -1
  375. package/scripts/time-runtime/time-instant.ts +3 -1
  376. package/scripts/time-runtime/time-spans.ts +6 -139
  377. package/scripts/time-runtime/time-tool-call.ts +160 -0
  378. package/scripts/version/effective-upstream.ts +2 -20
  379. package/scripts/version/latest-corepack.ts +17 -17
  380. package/scripts/version/latest-regression.ts +1 -1
  381. package/scripts/version/latest-update.ts +4 -4
  382. package/scripts/version/npm-registry.ts +20 -5
  383. package/scripts/version/publishable-range-check.ts +2 -2
  384. package/scripts/version/publishable-range.ts +1 -1
  385. package/scripts/version/release-age.ts +48 -38
  386. package/scripts/version/release-hold.ts +1 -1
  387. package/scripts/version/safe-chain-version-update.ts +40 -0
  388. package/scripts/version/upgrade-shell-command.ts +48 -8
  389. package/scripts/version/version-check-logic.ts +23 -14
  390. package/scripts/version/version-commands.ts +23 -11
  391. package/scripts/version/version-remote.ts +28 -13
  392. package/templates/pnpm-workspace.basic.yaml +4 -0
  393. package/templates/workflows/dependabot-auto-merge.yml +5 -5
  394. package/templates/workflows/github-release.yml +2 -0
  395. package/scripts/build/build-config-merge.ts +0 -27
  396. package/scripts/build/build-managed-marker.ts +0 -27
  397. package/scripts/build/build-self-sync-guard.ts +0 -27
  398. package/scripts/build/build-version.ts +0 -26
  399. package/scripts/version/preinstall-version-update.ts +0 -86
  400. /package/scripts/git/{schemas.ts → git-schemas.ts} +0 -0
  401. /package/scripts/overrides/{schemas.ts → overrides-schemas.ts} +0 -0
  402. /package/scripts/run/{run-carry-conversation.ts → carry/run-carry-conversation.ts} +0 -0
  403. /package/scripts/run/{run-carry-reclaim.ts → carry/run-carry-reclaim.ts} +0 -0
  404. /package/scripts/run/{run-carry-stash.ts → carry/run-carry-stash.ts} +0 -0
  405. /package/scripts/run/{run-carry-streak.ts → carry/run-carry-streak.ts} +0 -0
  406. /package/scripts/run/{run-cut-handoff.ts → cut/run-cut-handoff.ts} +0 -0
  407. /package/scripts/run/{run-cut-report.ts → cut/run-cut-report.ts} +0 -0
  408. /package/scripts/run/{run-entry-stop.ts → entry/run-entry-stop.ts} +0 -0
  409. /package/scripts/run/{run-event-cli.ts → event/run-event-cli.ts} +0 -0
  410. /package/scripts/run/{run-event-follow.ts → event/run-event-follow.ts} +0 -0
  411. /package/scripts/run/{run-event-render.ts → event/run-event-render.ts} +0 -0
  412. /package/scripts/run/{run-event-watch.ts → event/run-event-watch.ts} +0 -0
  413. /package/scripts/run/{run-hold.ts → hold/run-hold.ts} +0 -0
  414. /package/scripts/run/{run-progress-args.ts → progress/run-progress-args.ts} +0 -0
  415. /package/scripts/run/{run-progress-clock.ts → progress/run-progress-clock.ts} +0 -0
  416. /package/scripts/run/{run-review-cli.ts → review/run-review-cli.ts} +0 -0
  417. /package/scripts/run/{run-review.ts → review/run-review.ts} +0 -0
  418. /package/scripts/run/{run-ship-next.ts → ship/run-ship-next.ts} +0 -0
  419. /package/scripts/run/{run-ship-pre-detach.ts → ship/run-ship-pre-detach.ts} +0 -0
  420. /package/scripts/run/{run-ship-review.ts → ship/run-ship-review.ts} +0 -0
  421. /package/scripts/run/{run-ship-stage.ts → ship/run-ship-stage.ts} +0 -0
  422. /package/scripts/run/{run-ship.ts → ship/run-ship.ts} +0 -0
  423. /package/scripts/run/{run-tidy-cli.ts → tidy/run-tidy-cli.ts} +0 -0
  424. /package/scripts/run/{run-tidy-stashes.ts → tidy/run-tidy-stashes.ts} +0 -0
  425. /package/scripts/run/{run-tidy.ts → tidy/run-tidy.ts} +0 -0
@@ -111,12 +111,12 @@ function origin_section_lines(lines: ReadonlyArray<string>): Array<string> {
111
111
  return end === NOT_FOUND ? rest : rest.slice(0, end)
112
112
  }
113
113
 
114
- // The value of a `key = value` entry, or nothing when the line declares a different key. Split on
114
+ // The value of a `url = value` entry, or nothing when the line declares a different key. Split on
115
115
  // the first separator rather than matched, for the reason `parse_scp_like` is split by hand.
116
- function entry_value(line: string, key: string): string | undefined {
116
+ function url_value(line: string): string | undefined {
117
117
  const separator = line.indexOf('=')
118
118
  if (separator === NOT_FOUND) return undefined
119
- if (line.slice(0, separator).trim() !== key) return undefined
119
+ if (line.slice(0, separator).trim() !== URL_KEY) return undefined
120
120
 
121
121
  return line.slice(separator + 1).trim()
122
122
  }
@@ -129,7 +129,7 @@ function parse_origin_from_config(content: string): string | undefined {
129
129
  const lines = content.split('\n').map((line) => line.trim())
130
130
 
131
131
  return origin_section_lines(lines)
132
- .map((line) => entry_value(line, URL_KEY))
132
+ .map((line) => url_value(line))
133
133
  .find((url) => url !== undefined)
134
134
  }
135
135
 
@@ -153,7 +153,7 @@ function parse_remote_urls_from_config(content: string): Array<string> {
153
153
 
154
154
  return section_groups(lines)
155
155
  .filter((group) => REMOTE_SECTION.test(group[0] ?? ''))
156
- .flatMap((group) => group.map((line) => entry_value(line, URL_KEY)))
156
+ .flatMap((group) => group.map((line) => url_value(line)))
157
157
  .filter((url) => url !== undefined)
158
158
  }
159
159
 
@@ -3,6 +3,7 @@ import path from 'node:path'
3
3
  import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
4
4
  import { json_value } from '#scripts/lib/json-value'
5
5
  import { file_reader } from '#scripts/lib/read-file'
6
+ import { PROBE_TIMEOUT_MS } from '#scripts/lib/timeouts'
6
7
  import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
7
8
  import { z } from 'zod'
8
9
 
@@ -20,7 +21,6 @@ const PLUGIN_ID = 'kit@kit'
20
21
  // full document, so this pointer is also what tells a consumer repository apart from kit itself.
21
22
  const CLAUDE_MD_POINTER = `@${NODE_MODULES}/${KIT_PACKAGE_NAME}/dist/${CLAUDE_MD}`
22
23
  const SECRETLINT_BIN = path.join(NODE_MODULES, '.bin', 'secretlint')
23
- const GIT_TIMEOUT_MS = 2000
24
24
 
25
25
  const manifest_schema = z.object({
26
26
  name: z.string().optional(),
@@ -77,7 +77,7 @@ function is_plugin_declared(root: string): boolean {
77
77
  // from installing kit's git hooks — the failure is silent but for one stderr line.
78
78
  function configured_hooks_path(root: string): string | undefined {
79
79
  const value = git_spawn_sync.read(['-C', root, 'config', '--get', 'core.hooksPath'], {
80
- timeout: GIT_TIMEOUT_MS,
80
+ timeout: PROBE_TIMEOUT_MS,
81
81
  })
82
82
 
83
83
  return value === '' ? undefined : value
@@ -2,11 +2,11 @@ import { existsSync, readFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { git_spawn_sync } from '#scripts/git/git-spawn-sync'
4
4
  import { read_spawn_stdout } from '#scripts/lib/spawn-exit'
5
+ import { PROBE_TIMEOUT_MS } from '#scripts/lib/timeouts'
5
6
  import { execaSync } from 'execa'
6
7
  import { doctor_logic } from './doctor-logic'
7
8
 
8
9
  const JOSH_BIN = 'josh'
9
- const GIT_TIMEOUT_MS = 2000
10
10
  const NOT_A_REPOSITORY_MESSAGE = 'not a git repository'
11
11
  const NPM_VERSION_UPDATES_DISABLED = 'open-pull-requests-limit: 0'
12
12
  const DEPENDABOT_CONFIG_PATH = '.github/dependabot.yml'
@@ -72,7 +72,7 @@ function classify_git_failure(exit_code: number | undefined, stderr: string): Gi
72
72
  function resolve_git_top_level(): GitTopLevel {
73
73
  // The C locale `toplevel` asks under is what keeps the stderr match below from failing on a
74
74
  // translated message and bringing the spurious warning back.
75
- const result = git_spawn_sync.toplevel(GIT_TIMEOUT_MS)
75
+ const result = git_spawn_sync.toplevel(PROBE_TIMEOUT_MS)
76
76
 
77
77
  if (result.exit_code === 0) return classify_top_level(result.stdout.trim())
78
78
 
@@ -194,7 +194,6 @@ type GitTopLevel =
194
194
  { state: 'inside'; top_level: string } | { state: 'outside' } | { state: 'undetermined' }
195
195
 
196
196
  const doctor_io = {
197
- GIT_TIMEOUT_MS,
198
197
  resolve_path_josh,
199
198
  resolve_pnpm_global_josh,
200
199
  has_distributed_dependabot_config,
@@ -0,0 +1,30 @@
1
+ import path from 'node:path'
2
+
3
+ // Where `capped-print.ts` writes the parts of an over-cap output, and how a path is recognized as one
4
+ // of them (joshuafolkken/kit#3332). Both halves live here so the writer and the investigation guard,
5
+ // which must not count a read of a part as reading the Issue's subject, share one spelling of the
6
+ // shape — kept free of the writer's own imports, since the guard runs as a hook on every tool call.
7
+
8
+ const DIRECTORY_PREFIX = 'josh-capped-print-'
9
+ const PART_PREFIX = 'part-'
10
+ const PART_SUFFIX = '.md'
11
+ // A part is `part-<N>.md` and nothing else, so a run's own file that happens to sit beside one is not
12
+ // mistaken for it.
13
+ const PART_PATTERN = /^part-[1-9]\d*\.md$/u
14
+
15
+ function part_name(index: number): string {
16
+ return `${PART_PREFIX}${String(index + 1)}${PART_SUFFIX}`
17
+ }
18
+
19
+ // **The file's own parent, and the file's own name, both have to match.** A directory prefix alone
20
+ // would exempt anything a run unpacked under a similarly named directory; the caller also requires the
21
+ // path to lie outside the checkout, so no repository file is ever exempted by its name.
22
+ function is_part_file(absolute: string): boolean {
23
+ const parent = path.basename(path.dirname(absolute))
24
+
25
+ return parent.startsWith(DIRECTORY_PREFIX) && PART_PATTERN.test(path.basename(absolute))
26
+ }
27
+
28
+ const capped_print_part = { DIRECTORY_PREFIX, part_name, is_part_file }
29
+
30
+ export { capped_print_part }
@@ -3,6 +3,7 @@ import path from 'node:path'
3
3
  import { PLATFORM_TEMP_ROOT } from '#scripts/josh/platform-temporary'
4
4
  import { review_diff_parts } from '#scripts/review/review-diff-parts'
5
5
  import { bash_output_cap_reader } from './bash-output-cap'
6
+ import { capped_print_part } from './capped-print-part'
6
7
 
7
8
  // Prints a command's output whole when it fits under the Bash output cap, and as part files when it
8
9
  // does not (joshuafolkken/kit#3143). Past `BASH_MAX_OUTPUT_LENGTH` Claude Code saves the output and
@@ -17,9 +18,7 @@ import { bash_output_cap_reader } from './bash-output-cap'
17
18
  // in one turn, and a shared directory replaced on every call would delete one call's parts while
18
19
  // another call's listing still names them.
19
20
 
20
- const DIRECTORY_PREFIX = 'josh-capped-print-'
21
21
  const FILE_MODE = 0o600
22
- const PART_SUFFIX = '.md'
23
22
  const LINE_BREAK = '\n'
24
23
  // What else shares the Bash result with this output: the launcher line, a script banner, a few
25
24
  // stderr lines. Measured at about 80 characters for `pnpm josh doc:section` in kit, so 512 holds the
@@ -27,10 +26,10 @@ const LINE_BREAK = '\n'
27
26
  const HEADROOM_CHARS = 512
28
27
 
29
28
  function write_parts(label: string, text: string, cap: number): Array<string> {
30
- const directory = mkdtempSync(path.join(PLATFORM_TEMP_ROOT, DIRECTORY_PREFIX))
29
+ const directory = mkdtempSync(path.join(PLATFORM_TEMP_ROOT, capped_print_part.DIRECTORY_PREFIX))
31
30
 
32
31
  return review_diff_parts.part_texts(label, text, cap).map((part, index) => {
33
- const file = path.join(directory, `part-${String(index + 1)}${PART_SUFFIX}`)
32
+ const file = path.join(directory, capped_print_part.part_name(index))
34
33
 
35
34
  writeFileSync(file, part, { flag: 'wx', mode: FILE_MODE })
36
35
 
@@ -88,8 +88,17 @@ const DOCUMENT_BYTE_BUDGET: ReadonlyArray<DocumentBudget> = [
88
88
  // joshuafolkken/kit#2998 split the reference: the developer commands stay, the rest moved out.
89
89
  // joshuafolkken/kit#3175 raised it: the supervisor, provider-table and answer-to-budget prose moved
90
90
  // here out of `backlogrun-steps.md`, so a run reads it only when it asks about those commands.
91
- { path: 'docs/josh-commands-automation.md', bytes: 159_744 },
92
- { path: 'docs/josh-commands.md', bytes: 36_864 },
91
+ // joshuafolkken/kit#3277 moved the run, lane and session commands to `docs/josh-commands-run.md`.
92
+ // joshuafolkken/kit#3280 moved the issue, epic, backlog and review commands to
93
+ // `docs/josh-commands-backlog.md`.
94
+ // joshuafolkken/kit#3278 moved `josh latest` and `josh overrides` — commands a person types — from
95
+ // the automation page to `docs/josh-commands.md`, which grew one block and the automation page shrank one.
96
+ { path: 'docs/josh-commands-automation.md', bytes: 49_152 },
97
+ { path: 'docs/josh-commands-run.md', bytes: 57_344 },
98
+ { path: 'docs/josh-commands-backlog.md', bytes: 57_344 },
99
+ { path: 'docs/josh-commands.md', bytes: 40_960 },
100
+ // joshuafolkken/kit#3279 moved the label sections here out of `docs/josh-commands-backlog.md`.
101
+ { path: 'docs/labels-and-run-states.md', bytes: 8192 },
93
102
  { path: 'prompts/coding-standards.md', bytes: 8192 },
94
103
  { path: 'prompts/collaboration-workflow.md', bytes: 8192 },
95
104
  { path: 'prompts/collaboration-workflow/file-edits.md', bytes: 12_288 },
@@ -0,0 +1,117 @@
1
+ // Pulling machine-checkable references out of the distributed documents, so a few structural scans
2
+ // can replace the ~140 per-phrase marker suites the documents used to carry (joshuafolkken/kit#1923).
3
+ // Every function here is a pure string extractor: it takes document text and returns the references
4
+ // it names, which is what lets the scans that call it be checked on a synthetic string with a
5
+ // deliberately broken reference in it.
6
+
7
+ // A command reference, only inside a code span. `` `pnpm josh gate` `` and `` `josh review:brief` ``
8
+ // are the copy-and-paste form; the leading backtick keeps prose like "josh will decide" out, which
9
+ // would otherwise read "will" as a command name.
10
+ const COMMAND_PATTERN = /`(?:pnpm )?josh ([a-z][a-z0-9:-]+)/gu
11
+
12
+ // A label the documents operate on — applied (`labels[]=…`), removed (`…/labels/<name>`) or created
13
+ // (`labels -f name=…`). Prose that merely names a label is not scanned; a mistyped name only breaks
14
+ // where a command carries it.
15
+ const LABEL_PATTERN = /(?:labels\[\]=|\/labels\/|labels -f name=)([a-z][a-z0-9:_-]*)/gu
16
+
17
+ // A section reference: a markdown file, an arrow, and the cited heading in straight or Japanese
18
+ // quotes — `` `backlogrun-progress.md` → "The hand-off" ``. The code span is optional and a section
19
+ // number may sit between the arrow and the heading (`` `SKILL.md` → §1, "…" ``): both forms are in
20
+ // the documents, as is a name linked to its file (`` [`residency.md`](./residency.md) →「…」 ``), and a
21
+ // pattern that skipped them let a broken reference of each form through (joshuafolkken/kit#3076).
22
+ // The backreference pairs the code span's backticks, and the lookbehind keeps a bare name from
23
+ // starting mid-path — inside a URL or after an unpaired backtick.
24
+ const SECTION_PATTERN =
25
+ /(?<![\w./:`-])(?<tick>`?)(?<file>[\w./-]+\.md)\k<tick>\s*→\s*(?:§\w+,\s*)?["「](?<heading>[^"」]+)["」]/gu
26
+
27
+ // A markdown link's `(target)`, dropped before the section scan so a linked name sits right before its
28
+ // arrow — the link text already carries the file the reference names.
29
+ const LINK_TARGET_PATTERN = /\]\([^)\s]*\)/gu
30
+
31
+ // A relative markdown link target, with an optional `#anchor`. External `https://…` links carry no
32
+ // `.md`, so the pattern never matches them.
33
+ const LINK_PATTERN = /\]\(([\w./-]+\.md)(?:#[\w-]+)?\)/gu
34
+
35
+ // Filenames that appear in the documents as illustrations of the reference form itself, never as real
36
+ // targets — the doc-section explanation cites `` `path.md` → "Heading" `` and `` `X.md` → "Heading" ``.
37
+ const PLACEHOLDER_FILES: ReadonlySet<string> = new Set(['x.md', 'foo.md', 'file.md', 'path.md'])
38
+
39
+ function matches(text: string, pattern: RegExp): Array<RegExpMatchArray> {
40
+ return [...text.matchAll(pattern)]
41
+ }
42
+
43
+ // A trailing colon is a placeholder for the sub-command that follows — `` `josh epic:` `` stands for
44
+ // `epic:audit`, `epic:next` and the rest — so it names no command of its own.
45
+ function command_references(text: string): Array<string> {
46
+ return matches(text, COMMAND_PATTERN)
47
+ .map((match) => match[1] ?? '')
48
+ .filter((name) => !name.endsWith(':'))
49
+ }
50
+
51
+ // A `<placeholder>` inside a command, and `depth:<n>` after the char class stops at `<`, both leave a
52
+ // token that is not a real label. Real labels never end in a colon and never carry angle brackets.
53
+ function is_real_label(token: string): boolean {
54
+ return token.length > 0 && !token.endsWith(':') && !token.includes('<')
55
+ }
56
+
57
+ function label_references(text: string): Array<string> {
58
+ return matches(text, LABEL_PATTERN)
59
+ .map((match) => match[1] ?? '')
60
+ .filter((token) => is_real_label(token))
61
+ }
62
+
63
+ function base_name(path: string): string {
64
+ return path.split('/').at(-1) ?? path
65
+ }
66
+
67
+ function is_placeholder_file(name: string): boolean {
68
+ return name.includes('<') || PLACEHOLDER_FILES.has(base_name(name).toLowerCase())
69
+ }
70
+
71
+ interface SectionReference {
72
+ file: string
73
+ heading: string
74
+ }
75
+
76
+ function section_references(text: string): Array<SectionReference> {
77
+ return matches(text.replaceAll(LINK_TARGET_PATTERN, ''), SECTION_PATTERN)
78
+ .map(({ groups }) => ({ file: groups?.['file'] ?? '', heading: groups?.['heading'] ?? '' }))
79
+ .filter((reference) => !is_placeholder_file(reference.file))
80
+ }
81
+
82
+ function link_targets(text: string): Array<string> {
83
+ return matches(text, LINK_PATTERN)
84
+ .map((match) => match[1] ?? '')
85
+ .filter((file) => !is_placeholder_file(file))
86
+ }
87
+
88
+ // A cited heading and the heading it names are compared with backticks dropped and whitespace
89
+ // collapsed, because a reference wraps at the column the prose wraps at — sometimes across a
90
+ // blockquote `> ` continuation — while the heading it points at is one unbroken line.
91
+ function normalize_reference(heading: string): string {
92
+ return heading
93
+ .replaceAll('`', '')
94
+ .split(/\s+/u)
95
+ .filter((word) => word !== '>')
96
+ .join(' ')
97
+ }
98
+
99
+ // The two forms a heading can be cited by: with its leading number (`2b. Delegating…`) and without
100
+ // (`Delegating…`), since a document's own numbering is a prefix a reference may or may not carry.
101
+ function reference_forms(title: string): Array<string> {
102
+ const normalized = normalize_reference(title)
103
+
104
+ return [normalized, normalized.replace(/^\d+[a-z]?\. /u, '')]
105
+ }
106
+
107
+ const document_scan = {
108
+ command_references,
109
+ label_references,
110
+ link_targets,
111
+ normalize_reference,
112
+ reference_forms,
113
+ section_references,
114
+ }
115
+
116
+ export type { SectionReference }
117
+ export { document_scan }
@@ -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 },