@joshuafolkken/kit 1.1784.0 → 1.2116.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (690) hide show
  1. package/.claude/agents/reviewer-verification.md +19 -0
  2. package/.claude/agents/reviewer.md +19 -0
  3. package/.claude/settings.json +5 -6
  4. package/.claude/skills/dependency-update/SKILL.md +9 -10
  5. package/.claude/skills/workflow-commands/SKILL.md +6 -6
  6. package/.claude/skills/workflow-commands/background-commands.md +8 -5
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -1
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +5 -2
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +2 -3
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +1 -1
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +3 -2
  13. package/.claude/skills/workflow-commands/backlogrun.md +1 -1
  14. package/.claude/skills/workflow-commands/chain-rule.md +41 -32
  15. package/.claude/skills/workflow-commands/delegation.md +6 -6
  16. package/.claude/skills/workflow-commands/entry-sequence.md +2 -2
  17. package/.claude/skills/workflow-commands/followup.md +7 -5
  18. package/.claude/skills/workflow-commands/fullrun-steps.md +6 -6
  19. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  20. package/.claude/skills/workflow-commands/issue-comments.md +9 -4
  21. package/.claude/skills/workflow-commands/issue-fold-existing.md +2 -1
  22. package/.claude/skills/workflow-commands/kickoff.md +2 -2
  23. package/.claude/skills/workflow-commands/observation-filing.md +1 -1
  24. package/.claude/skills/workflow-commands/observation-ledger.md +6 -4
  25. package/.claude/skills/workflow-commands/pre-gate-cut.md +9 -7
  26. package/.claude/skills/workflow-commands/prerequisite.md +3 -2
  27. package/.claude/skills/workflow-commands/retrospective.md +2 -2
  28. package/.claude/skills/workflow-commands/split-assessment.md +7 -5
  29. package/.codex/hooks.json +5 -5
  30. package/.github/actions/setup-pnpm/action.yml +9 -16
  31. package/.github/dependabot.yml +16 -0
  32. package/.github/workflows/auto-tag.yml +45 -0
  33. package/.github/workflows/ci.yml +155 -15
  34. package/.github/workflows/dependabot-auto-merge.yml +63 -4
  35. package/.github/workflows/production.yml +8 -0
  36. package/.github/workflows/publish.yml +60 -45
  37. package/.vscode/settings.json +19 -0
  38. package/CLAUDE.md +5 -12
  39. package/SECURITY.md +2 -2
  40. package/dist/CLAUDE.md +5 -12
  41. package/dist/commands/document-read-cli.js +2 -1
  42. package/dist/commands/document-section-main.js +2 -1
  43. package/dist/commands/read-set-cli.js +194 -134
  44. package/dist/commands/run-prep-main.js +9694 -10173
  45. package/dist/commands/run-step-cli.js +22138 -18285
  46. package/dist/hooks/audit-provision.js +23 -5
  47. package/dist/hooks/chunk-3GKE653K.js +6204 -0
  48. package/dist/hooks/chunk-45CJJOO7.js +1243 -0
  49. package/dist/hooks/{chunk-CWMBGKYW.js → chunk-5PM2JRGV.js} +2153 -856
  50. package/dist/hooks/{chunk-WR2KQAYZ.js → chunk-7SIUNQXK.js} +1207 -1470
  51. package/dist/hooks/{chunk-O2YBIDSI.js → chunk-CQD4LJE6.js} +1682 -1130
  52. package/dist/hooks/{chunk-VWWCFGWK.js → chunk-OPSRGWFU.js} +1 -1
  53. package/dist/hooks/{chunk-XGBNSWEI.js → chunk-UJ3FPMMM.js} +132 -49
  54. package/dist/hooks/chunk-VVNYJWEI.js +30 -0
  55. package/dist/hooks/chunk-Z6DQJKH3.js +2806 -0
  56. package/dist/hooks/codex-hook-adapter.js +8 -8
  57. package/dist/hooks/format-edited.js +4 -5
  58. package/dist/hooks/inputs.json +386 -360
  59. package/dist/hooks/pretool-guard.js +7 -6
  60. package/dist/hooks/session-lang.js +101 -258
  61. package/dist/hooks/stop-guard.js +1744 -2576
  62. package/dist/josh.js +7133 -6368
  63. package/dist/version/index.js +161 -1
  64. package/eslint/rules/comment-why-only.js +108 -0
  65. package/package.json +16 -9
  66. package/prompts/coding-standards.md +1 -1
  67. package/prompts/collaboration-workflow/file-edits.md +14 -11
  68. package/prompts/collaboration-workflow/issue-citation.md +0 -4
  69. package/prompts/collaboration-workflow/issue-template.md +40 -21
  70. package/prompts/collaboration-workflow/lane-instructions.md +3 -1
  71. package/prompts/collaboration-workflow/measurement.md +8 -1
  72. package/prompts/collaboration-workflow/operating-rules.md +11 -2
  73. package/prompts/collaboration-workflow/output-bounds.md +6 -2
  74. package/prompts/collaboration-workflow/plan-comment.md +1 -1
  75. package/prompts/collaboration-workflow/principles.md +9 -0
  76. package/prompts/collaboration-workflow/residency.md +3 -2
  77. package/prompts/collaboration-workflow/rule-delivery.md +13 -1
  78. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  79. package/prompts/collaboration-workflow/wip-cap.md +3 -1
  80. package/prompts/review-rubric.md +8 -0
  81. package/prompts/review.md +31 -22
  82. package/prompts/testing-guide.md +1 -0
  83. package/scripts/agent/agent-argv.ts +14 -16
  84. package/scripts/agent/agent-provider.ts +138 -0
  85. package/scripts/agent/agent-role-profile.ts +108 -156
  86. package/scripts/agent/agent-safe-text.ts +20 -5
  87. package/scripts/agent/api-usage-limit.ts +65 -0
  88. package/scripts/agent/claude-agent-argv.ts +19 -9
  89. package/scripts/agent/codex-agent-argv.ts +118 -22
  90. package/scripts/agent/usage-limit-reset.ts +179 -0
  91. package/scripts/auth/claude-token-output.ts +2 -2
  92. package/scripts/auto-ok/auto-ok-author.ts +53 -0
  93. package/scripts/auto-ok/auto-ok-cli.ts +20 -9
  94. package/scripts/backlog/backlog-defect-priority.ts +1 -1
  95. package/scripts/backlog/backlog-drive-cli.ts +6 -1
  96. package/scripts/backlog/backlog-drive-hold.ts +3 -3
  97. package/scripts/backlog/backlog-drive-idle.ts +10 -0
  98. package/scripts/backlog/backlog-drive-launch-pass.ts +90 -0
  99. package/scripts/backlog/backlog-drive-launch.ts +8 -2
  100. package/scripts/backlog/backlog-drive-named-offer.ts +73 -30
  101. package/scripts/backlog/backlog-drive-named.ts +28 -7
  102. package/scripts/backlog/backlog-drive-paused.ts +5 -4
  103. package/scripts/backlog/backlog-drive-restore.ts +11 -3
  104. package/scripts/backlog/backlog-drive.ts +21 -60
  105. package/scripts/backlog/backlog-gate-scope.ts +61 -0
  106. package/scripts/backlog/backlog-measure-close.ts +112 -0
  107. package/scripts/backlog/backlog-next.ts +39 -49
  108. package/scripts/backlog/backlog-occupied.ts +2 -2
  109. package/scripts/backlog/backlog-overlap.ts +42 -8
  110. package/scripts/backlog/backlog-plan-cli.ts +4 -1
  111. package/scripts/backlog/backlog-plan-comment.ts +81 -0
  112. package/scripts/backlog/backlog-plan-read.ts +19 -6
  113. package/scripts/backlog/backlog-pool.ts +50 -12
  114. package/scripts/backlog/backlog-rank.ts +60 -13
  115. package/scripts/backlog/backlog-stalled-detect.ts +2 -1
  116. package/scripts/backlog/backlog-waves.ts +17 -2
  117. package/scripts/backlog/backlogrun-launch.ts +7 -4
  118. package/scripts/cost-runtime/context-cut-payback.ts +27 -25
  119. package/scripts/cost-runtime/cost-cli.ts +37 -16
  120. package/scripts/cost-runtime/cost-pricing.ts +9 -0
  121. package/scripts/cost-runtime/cost-verdict.ts +4 -1
  122. package/scripts/delegation/delegation-policy.ts +2 -2
  123. package/scripts/delegation/duplicate-read-guard.ts +1 -0
  124. package/scripts/delegation/investigation-guard.ts +14 -2
  125. package/scripts/delegation/investigation-reads.ts +45 -23
  126. package/scripts/dependabot/dependabot-cooldown.ts +3 -2
  127. package/scripts/discovery/repo-party.ts +80 -20
  128. package/scripts/discovery/session-owner-pin.ts +51 -0
  129. package/scripts/document/capped-print.ts +1 -1
  130. package/scripts/document/command-reference-line-budget.ts +42 -0
  131. package/scripts/document/document-byte-budget.ts +17 -7
  132. package/scripts/document/document-checks.ts +20 -0
  133. package/scripts/document/document-ratchet-check.ts +101 -0
  134. package/scripts/document/document-section.ts +1 -0
  135. package/scripts/document/entry-read-budget.ts +7 -3
  136. package/scripts/document/issue-citation-budget.ts +94 -0
  137. package/scripts/epic/epic-cli-argv.ts +2 -2
  138. package/scripts/epic/epic-fetch.ts +4 -2
  139. package/scripts/epic/epic-graph.ts +7 -2
  140. package/scripts/epic/epic-issue.ts +3 -1
  141. package/scripts/epic/epic-rank.ts +5 -0
  142. package/scripts/exports/unused-members-check.ts +62 -0
  143. package/scripts/exports/unused-members-cli.ts +6 -51
  144. package/scripts/followup/followup-ci-path.ts +67 -0
  145. package/scripts/followup/followup-coderabbit-retrigger.ts +261 -0
  146. package/scripts/followup/followup-coderabbit-wait.ts +13 -10
  147. package/scripts/followup/followup-failed-tests.ts +160 -0
  148. package/scripts/followup/followup-job-needs.ts +100 -0
  149. package/scripts/followup/followup-job-rerun.ts +303 -0
  150. package/scripts/followup/followup-main-move.ts +113 -0
  151. package/scripts/followup/git-followup-finish.ts +43 -11
  152. package/scripts/followup/git-followup-pending.ts +3 -3
  153. package/scripts/followup/git-followup-stages.ts +41 -11
  154. package/scripts/followup/git-pr-followup-checks.ts +55 -16
  155. package/scripts/followup/git-pr-followup-wrapup.ts +24 -5
  156. package/scripts/followup/git-pr-followup.ts +62 -27
  157. package/scripts/fresh-machine/fresh-machine-script.ts +5 -9
  158. package/scripts/fresh-machine/specs/kit-test-backlogrun-python.json +2 -2
  159. package/scripts/fresh-machine/specs/kit-test-backlogrun.json +2 -2
  160. package/scripts/gate/busy-average.ts +34 -2
  161. package/scripts/gate/check-related-pinned.ts +43 -0
  162. package/scripts/gate/check-related-reuse.ts +64 -0
  163. package/scripts/gate/check-related-section.ts +57 -0
  164. package/scripts/gate/check-related-unplanned.ts +34 -0
  165. package/scripts/gate/check-related.ts +265 -0
  166. package/scripts/gate/command-memory.ts +1 -1
  167. package/scripts/gate/core-admission.ts +24 -6
  168. package/scripts/gate/core-budget-timing.ts +20 -6
  169. package/scripts/gate/core-budget.ts +40 -11
  170. package/scripts/gate/core-wait-record.ts +3 -2
  171. package/scripts/gate/core-wait.ts +25 -2
  172. package/scripts/gate/core-weights.ts +15 -7
  173. package/scripts/gate/gate-labels.ts +46 -0
  174. package/scripts/gate/gate-ledger.ts +14 -6
  175. package/scripts/gate/gate-plan.ts +103 -58
  176. package/scripts/gate/gate-report.ts +9 -7
  177. package/scripts/gate/gate-run-steps.ts +26 -3
  178. package/scripts/gate/gate-skip.ts +45 -6
  179. package/scripts/gate/gate-static-group.ts +60 -0
  180. package/scripts/gate/gate-steps.ts +2 -1
  181. package/scripts/gate/gate-tree.ts +4 -2
  182. package/scripts/gate/hook-gate-reuse.ts +19 -2
  183. package/scripts/gate/metrics-totals.ts +59 -0
  184. package/scripts/gate/scoped-green.ts +68 -15
  185. package/scripts/gate/since-green.ts +14 -6
  186. package/scripts/gate/structural-checks.ts +96 -0
  187. package/scripts/gate/verification-gate.ts +51 -16
  188. package/scripts/gate/verification-inputs.ts +21 -6
  189. package/scripts/gate/verification-tail.ts +52 -0
  190. package/scripts/gh/gh-author-association.ts +23 -2
  191. package/scripts/gh/git-ai-review-scan.ts +1 -1
  192. package/scripts/gh/git-gh-api-path.ts +12 -3
  193. package/scripts/gh/git-gh-comments-unreadable.ts +36 -0
  194. package/scripts/gh/git-gh-exec.ts +32 -2
  195. package/scripts/gh/git-gh-issue-read.ts +4 -2
  196. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  197. package/scripts/gh/git-gh-issue-write.ts +8 -2
  198. package/scripts/gh/git-gh-issue.ts +12 -5
  199. package/scripts/gh/git-gh-pr-auto-merge.ts +13 -6
  200. package/scripts/gh/git-gh-pr-graphql-state.ts +56 -0
  201. package/scripts/gh/git-gh-pr-read.ts +74 -13
  202. package/scripts/gh/git-gh-pr-rest.ts +3 -1
  203. package/scripts/gh/git-gh-pr-snapshot.ts +22 -7
  204. package/scripts/gh/git-gh-pr-too-large.ts +20 -0
  205. package/scripts/gh/git-gh-pr.ts +21 -7
  206. package/scripts/gh/git-gh-retry.ts +141 -0
  207. package/scripts/gh/git-pr-ai-review.ts +15 -6
  208. package/scripts/gh/git-pr-checks-eval.ts +3 -0
  209. package/scripts/gh/git-pr-checks.ts +5 -5
  210. package/scripts/git/base-tree.ts +30 -0
  211. package/scripts/git/change-base.ts +4 -10
  212. package/scripts/git/changed-paths-sync.ts +63 -0
  213. package/scripts/git/changed-paths.ts +16 -1
  214. package/scripts/git/git-common-directory.ts +11 -1
  215. package/scripts/git/git-diff-reads.ts +66 -20
  216. package/scripts/git/git-error.ts +9 -2
  217. package/scripts/git/git-schemas.ts +13 -0
  218. package/scripts/git/git-worktree.ts +13 -0
  219. package/scripts/git/gone-branch.ts +4 -1
  220. package/scripts/git/main-merge-budget-conflict.ts +89 -0
  221. package/scripts/git/main-merge-guard.ts +55 -18
  222. package/scripts/git/main-merge-regenerate.ts +59 -25
  223. package/scripts/git/main-merge.ts +53 -5
  224. package/scripts/git/no-base-checkout.ts +127 -0
  225. package/scripts/git/stash/git-stash.ts +11 -2
  226. package/scripts/git/stash/stash-push-cli.ts +10 -0
  227. package/scripts/hooks/batch-guard.ts +40 -21
  228. package/scripts/hooks/edited-cspell.ts +37 -11
  229. package/scripts/hooks/format-edited-file.ts +22 -2
  230. package/scripts/hooks/lane-plan-guard.ts +50 -0
  231. package/scripts/hooks/pre-commit-lint-switch.ts +9 -0
  232. package/scripts/hooks/pre-commit-lint.ts +14 -1
  233. package/scripts/hooks/pre-push-related-switch.ts +10 -0
  234. package/scripts/hooks/pre-push-unit.ts +76 -10
  235. package/scripts/hooks/pretool-guard.ts +2 -0
  236. package/scripts/hooks/reserved-run.ts +14 -1
  237. package/scripts/hooks/run-hook.sh +23 -8
  238. package/scripts/hooks/step-zero-notice.ts +6 -1
  239. package/scripts/hooks/stop-actions.ts +3 -15
  240. package/scripts/hooks/stop-guard.ts +7 -6
  241. package/scripts/init/distributed-paths.ts +1 -0
  242. package/scripts/init/hook-command-rewrite.ts +5 -1
  243. package/scripts/init/hook-launch.ts +4 -2
  244. package/scripts/issue/aggregation-split.ts +52 -0
  245. package/scripts/issue/defect-origin-line.ts +19 -1
  246. package/scripts/issue/defect-rate-cli.ts +5 -102
  247. package/scripts/issue/defect-report.ts +4 -4
  248. package/scripts/issue/effect-audit-cli.ts +178 -0
  249. package/scripts/issue/effect-audit.ts +185 -0
  250. package/scripts/issue/expected-effect-lint.ts +64 -9
  251. package/scripts/issue/git-next-issues.ts +5 -0
  252. package/scripts/issue/issue-body-or-comment.ts +38 -4
  253. package/scripts/issue/issue-cite-cli.ts +1 -3
  254. package/scripts/issue/issue-classification.ts +1 -0
  255. package/scripts/issue/issue-closing-pr.ts +8 -1
  256. package/scripts/issue/issue-evidence-exit.ts +4 -2
  257. package/scripts/issue/issue-file-cap.ts +58 -0
  258. package/scripts/issue/issue-file-cli.ts +111 -38
  259. package/scripts/issue/issue-file-deferral.ts +52 -0
  260. package/scripts/issue/issue-file-fold.ts +29 -10
  261. package/scripts/issue/issue-file-interrupt.ts +85 -0
  262. package/scripts/issue/issue-file-lane-hint.ts +30 -0
  263. package/scripts/issue/issue-file.ts +43 -6
  264. package/scripts/issue/issue-labels.ts +19 -2
  265. package/scripts/issue/issue-lint-cli.ts +27 -2
  266. package/scripts/issue/issue-measured-cli.ts +13 -35
  267. package/scripts/issue/issue-measured-close.ts +58 -0
  268. package/scripts/issue/issue-not-before-label.ts +2 -2
  269. package/scripts/issue/issue-not-before.ts +4 -1
  270. package/scripts/issue/issue-priority-cli.ts +60 -0
  271. package/scripts/issue/issue-priority-raise.ts +55 -0
  272. package/scripts/issue/issue-priority.ts +174 -43
  273. package/scripts/issue/issue-rank.ts +21 -4
  274. package/scripts/issue/issue-read-cli.ts +32 -6
  275. package/scripts/issue/issue-read.ts +37 -12
  276. package/scripts/issue/issue-sample-gate.ts +95 -74
  277. package/scripts/issue/issue-sample-prime.ts +127 -0
  278. package/scripts/issue/issue-sample-required.ts +57 -0
  279. package/scripts/issue/issue-sample-source.ts +12 -11
  280. package/scripts/issue/issue-search.ts +124 -0
  281. package/scripts/issue/issue-state.ts +13 -1
  282. package/scripts/issue/issue-template-section.ts +43 -0
  283. package/scripts/issue/issue-watch-diff.ts +31 -1
  284. package/scripts/issue/issue-watch-hook.ts +11 -2
  285. package/scripts/issue/issue-watch.ts +55 -12
  286. package/scripts/issue/markdown-section.ts +1 -0
  287. package/scripts/issue/measure-declaration.ts +121 -0
  288. package/scripts/issue/measure-rerun-cli.ts +7 -70
  289. package/scripts/issue/plan-consumers-section.ts +22 -1
  290. package/scripts/issue/reproduction-measure.ts +3 -1
  291. package/scripts/issue/size-estimate.ts +90 -0
  292. package/scripts/issue/trusted-measure.ts +84 -0
  293. package/scripts/josh/command-usage.ts +3 -2
  294. package/scripts/josh/durable-state-migrate.ts +105 -0
  295. package/scripts/josh/durable-state.ts +112 -0
  296. package/scripts/josh/josh-aliases.ts +95 -0
  297. package/scripts/josh/josh-command-map.ts +1 -82
  298. package/scripts/josh/josh-commands-agent-hooks.ts +6 -6
  299. package/scripts/josh/josh-commands-ai.ts +5 -5
  300. package/scripts/josh/josh-commands-development.ts +59 -58
  301. package/scripts/josh/josh-commands-issue-write.ts +23 -4
  302. package/scripts/josh/josh-commands-issue.ts +32 -0
  303. package/scripts/josh/josh-commands-lane.ts +87 -4
  304. package/scripts/josh/josh-commands-lint.ts +16 -3
  305. package/scripts/josh/josh-commands-maintenance.ts +15 -0
  306. package/scripts/josh/josh-commands-review.ts +41 -6
  307. package/scripts/josh/josh-commands-ship.ts +56 -8
  308. package/scripts/josh/josh-commands-test-check.ts +80 -0
  309. package/scripts/josh/josh-commands-workflow.ts +1 -1
  310. package/scripts/josh/josh-environment-file.ts +43 -3
  311. package/scripts/josh/josh-in-process.ts +2 -2
  312. package/scripts/josh/josh-logic.ts +5 -3
  313. package/scripts/josh/josh-run.ts +19 -1
  314. package/scripts/josh/run-record-path.ts +15 -0
  315. package/scripts/josh/stamp-file.ts +11 -2
  316. package/scripts/lane/lane-adopt-cli.ts +98 -0
  317. package/scripts/lane/lane-adopt.ts +208 -0
  318. package/scripts/lane/lane-await.ts +5 -8
  319. package/scripts/lane/lane-baseline.ts +123 -0
  320. package/scripts/lane/lane-breakdown-cli.ts +64 -0
  321. package/scripts/lane/lane-breakdown-implement.ts +170 -0
  322. package/scripts/lane/lane-breakdown-laps.ts +107 -0
  323. package/scripts/lane/lane-breakdown-pinned.ts +76 -0
  324. package/scripts/lane/lane-breakdown-text.ts +145 -0
  325. package/scripts/lane/lane-breakdown.ts +239 -0
  326. package/scripts/lane/lane-check-stats.ts +29 -8
  327. package/scripts/lane/lane-child-invocation.ts +9 -2
  328. package/scripts/lane/lane-child-mark-keys.ts +12 -0
  329. package/scripts/lane/lane-child-marker.ts +20 -1
  330. package/scripts/lane/lane-ci-path-stats.ts +73 -0
  331. package/scripts/lane/lane-claim-stats.ts +84 -0
  332. package/scripts/lane/lane-close.ts +8 -3
  333. package/scripts/lane/lane-dispatch-log.ts +3 -3
  334. package/scripts/lane/lane-dispatch.ts +2 -2
  335. package/scripts/lane/lane-gap-stats.ts +7 -6
  336. package/scripts/lane/lane-guard-policy.ts +7 -1
  337. package/scripts/lane/lane-implement-stats.ts +207 -0
  338. package/scripts/lane/lane-implement-subagents.ts +90 -0
  339. package/scripts/lane/lane-investigation-switch.ts +16 -0
  340. package/scripts/lane/lane-ledger-kind.ts +31 -0
  341. package/scripts/lane/lane-ledger-schema-ship.ts +49 -0
  342. package/scripts/lane/lane-ledger-schema.ts +119 -36
  343. package/scripts/lane/lane-ledger-write.ts +257 -0
  344. package/scripts/lane/lane-ledger.ts +31 -177
  345. package/scripts/lane/lane-load.ts +1 -1
  346. package/scripts/lane/lane-measure-cli.ts +176 -59
  347. package/scripts/lane/lane-net-read.ts +44 -0
  348. package/scripts/lane/lane-net-time.ts +212 -0
  349. package/scripts/lane/lane-open.ts +16 -7
  350. package/scripts/lane/lane-output.ts +2 -4
  351. package/scripts/lane/lane-overrun-prompt.ts +147 -0
  352. package/scripts/lane/lane-overrun.ts +274 -0
  353. package/scripts/lane/lane-paths.ts +24 -1
  354. package/scripts/lane/lane-pause-cli.ts +4 -1
  355. package/scripts/lane/lane-paused-spans.ts +97 -0
  356. package/scripts/lane/lane-plan-check.ts +207 -0
  357. package/scripts/lane/lane-plan-cli.ts +41 -0
  358. package/scripts/lane/lane-plan.ts +144 -0
  359. package/scripts/lane/lane-registry.ts +14 -1
  360. package/scripts/lane/lane-relaunch.ts +1 -1
  361. package/scripts/lane/lane-round-one-stats.ts +51 -0
  362. package/scripts/lane/lane-sampler.ts +23 -4
  363. package/scripts/lane/lane-session-action.ts +93 -18
  364. package/scripts/lane/lane-session-broker.ts +49 -21
  365. package/scripts/lane/lane-session-reread-main.ts +14 -0
  366. package/scripts/lane/lane-session-reread.ts +51 -0
  367. package/scripts/lane/lane-ship-lap-stats.ts +74 -0
  368. package/scripts/lane/lane-sleep.ts +58 -0
  369. package/scripts/lane/lane-spent.ts +1 -1
  370. package/scripts/lane/lane-split-stats.ts +28 -0
  371. package/scripts/lane/lane-stage-stats.ts +13 -1
  372. package/scripts/lane/lane-state.ts +41 -9
  373. package/scripts/lane/lane-stats-tables.ts +58 -0
  374. package/scripts/lane/lane-stats.ts +21 -5
  375. package/scripts/lane/lane-stop-stats.ts +51 -19
  376. package/scripts/lane/lane-tail-stats.ts +32 -0
  377. package/scripts/lane/lane-transcript-calls.ts +67 -10
  378. package/scripts/lane/lane-transcript-stats.ts +44 -12
  379. package/scripts/lane/lane-value-cell.ts +72 -0
  380. package/scripts/lane/lane-value.ts +118 -0
  381. package/scripts/lane/lane-withdraw-closed.ts +124 -0
  382. package/scripts/lane/lane-withdraw.ts +108 -31
  383. package/scripts/lane/lane-yield.ts +85 -0
  384. package/scripts/lane/openai-lane-ship.ts +12 -11
  385. package/scripts/lane/openai-lane-supervisor-decision.ts +6 -8
  386. package/scripts/lane/openai-lane-supervisor-owner.ts +5 -8
  387. package/scripts/lane/openai-lane-supervisor.ts +5 -4
  388. package/scripts/lib/poll.ts +38 -9
  389. package/scripts/lib/settled-together.ts +10 -0
  390. package/scripts/lib/shell-quote.ts +21 -0
  391. package/scripts/lib/token-shape.ts +111 -0
  392. package/scripts/lint/eslint-daemon-sources.ts +79 -0
  393. package/scripts/lint/eslint-daemon.ts +45 -6
  394. package/scripts/lint/eslint-warm-cli.ts +7 -0
  395. package/scripts/lint/eslint-warm.ts +66 -0
  396. package/scripts/lint/import-graph.ts +141 -11
  397. package/scripts/lint/lint-gate-scope.ts +2 -2
  398. package/scripts/lint/lint-parallel.ts +54 -6
  399. package/scripts/lint/lint-related.ts +154 -33
  400. package/scripts/lint/rename-cli.ts +179 -0
  401. package/scripts/lint/rename-identifier.ts +192 -0
  402. package/scripts/lint/type-only-imports.ts +58 -0
  403. package/scripts/notify/telegram-notify.ts +12 -3
  404. package/scripts/observations/observation-record.ts +42 -16
  405. package/scripts/observations/observations-flush.ts +32 -9
  406. package/scripts/overrides/overrides-check.ts +9 -1
  407. package/scripts/release/release-approval-prune.ts +7 -1
  408. package/scripts/repo/required-checks-logic.ts +4 -1
  409. package/scripts/repo/ruleset-check.ts +4 -3
  410. package/scripts/report/markdown-shape.ts +28 -0
  411. package/scripts/report/report-format-reference.ts +15 -1
  412. package/scripts/report/report-labels.ts +12 -1
  413. package/scripts/report/report-lint-child-summary.ts +94 -0
  414. package/scripts/report/report-lint-cli.ts +26 -7
  415. package/scripts/report/report-lint.ts +2 -20
  416. package/scripts/review/prose-stale-cli.ts +88 -0
  417. package/scripts/review/prose-stale-scan.ts +152 -0
  418. package/scripts/review/review-attest.ts +73 -23
  419. package/scripts/review/review-brief-cli.ts +68 -19
  420. package/scripts/review/review-brief-paths.ts +22 -0
  421. package/scripts/review/review-brief.ts +14 -7
  422. package/scripts/review/review-comment-carry.ts +47 -0
  423. package/scripts/review/review-comment-round.ts +85 -22
  424. package/scripts/review/review-finding-ledger.ts +109 -7
  425. package/scripts/review/review-finding-text.ts +43 -0
  426. package/scripts/review/review-interaction.ts +11 -0
  427. package/scripts/review/review-lane-handover.ts +68 -0
  428. package/scripts/review/review-lint-cli.ts +57 -0
  429. package/scripts/review/review-lint-findings.ts +92 -0
  430. package/scripts/review/review-lint-sections.ts +80 -0
  431. package/scripts/review/review-lint.ts +221 -0
  432. package/scripts/review/review-prior-findings.ts +102 -0
  433. package/scripts/review/review-readers-scan.ts +4 -2
  434. package/scripts/review/review-record-cli.ts +177 -54
  435. package/scripts/review/review-record-code-round.ts +55 -0
  436. package/scripts/review/review-record-comment.ts +60 -0
  437. package/scripts/review/review-record-interaction.ts +35 -0
  438. package/scripts/review/review-record-pinned.ts +108 -0
  439. package/scripts/review/review-record.ts +25 -1
  440. package/scripts/review/review-reviewed-tree.ts +307 -0
  441. package/scripts/review/review-round-one-closure.ts +62 -0
  442. package/scripts/review/review-round-reuse.ts +106 -0
  443. package/scripts/review/review-round2-cli.ts +91 -29
  444. package/scripts/review/review-round2-skip-comment.ts +55 -0
  445. package/scripts/review/review-round2.ts +24 -8
  446. package/scripts/review/review-stamps.ts +7 -3
  447. package/scripts/review/review-stop-log.ts +20 -6
  448. package/scripts/review/review-stops-cli.ts +2 -1
  449. package/scripts/review/review-submit-cli.ts +101 -0
  450. package/scripts/rules/agent-model.ts +1 -1
  451. package/scripts/rules/bash-triggers.ts +3 -3
  452. package/scripts/rules/concise-reply.ts +88 -0
  453. package/scripts/rules/credential-body.ts +77 -0
  454. package/scripts/rules/decision-oracle.ts +1 -1
  455. package/scripts/rules/delivered-rules.ts +36 -17
  456. package/scripts/rules/destructive-command.ts +3 -11
  457. package/scripts/rules/direct-issue-comment.ts +34 -12
  458. package/scripts/rules/direct-issue-read.ts +191 -0
  459. package/scripts/rules/direct-pr-create.ts +4 -24
  460. package/scripts/rules/early-heartbeat.ts +2 -0
  461. package/scripts/rules/edit-tool.ts +27 -0
  462. package/scripts/rules/file-edit-rows.ts +17 -0
  463. package/scripts/rules/file-rewrite.ts +146 -0
  464. package/scripts/rules/firing-point.ts +3 -2
  465. package/scripts/rules/gh-api.ts +128 -7
  466. package/scripts/rules/gh-repo-selector.ts +84 -0
  467. package/scripts/rules/git-force.ts +2 -2
  468. package/scripts/rules/guard-remedy.ts +14 -5
  469. package/scripts/rules/implementation-cut.ts +61 -28
  470. package/scripts/rules/investigator-foreground.ts +231 -0
  471. package/scripts/rules/issue-citation.ts +15 -60
  472. package/scripts/rules/issue-comments.ts +9 -4
  473. package/scripts/rules/josh-git-bare.ts +1 -1
  474. package/scripts/rules/lane-background.ts +41 -13
  475. package/scripts/rules/lane-direct-tool.ts +107 -9
  476. package/scripts/rules/lane-interactive-ask.ts +4 -1
  477. package/scripts/rules/lane-park.ts +4 -2
  478. package/scripts/rules/lane-spilled-read.ts +90 -0
  479. package/scripts/rules/lane-split-park.ts +1 -1
  480. package/scripts/rules/lane-switch-main.ts +4 -4
  481. package/scripts/rules/last-prompt.ts +20 -47
  482. package/scripts/rules/oracle-consulted.ts +1 -1
  483. package/scripts/rules/permission-guards.ts +5 -1
  484. package/scripts/rules/piped-verification-shapes.ts +110 -0
  485. package/scripts/rules/piped-verification.ts +67 -17
  486. package/scripts/rules/poll-loop.ts +72 -9
  487. package/scripts/rules/pr-merge.ts +5 -5
  488. package/scripts/rules/pre-gate-cut.ts +3 -3
  489. package/scripts/rules/protected-files.ts +88 -24
  490. package/scripts/rules/reply-language.ts +3 -17
  491. package/scripts/rules/reply-prose.ts +34 -0
  492. package/scripts/rules/repo-admin.ts +132 -0
  493. package/scripts/rules/reviewer-foreground.ts +88 -0
  494. package/scripts/rules/round-one-check-related.ts +120 -0
  495. package/scripts/rules/rule-body-guard.ts +5 -15
  496. package/scripts/rules/rule-value-cache.ts +53 -5
  497. package/scripts/rules/rule-value-candidates.ts +84 -0
  498. package/scripts/rules/rule-value-cli.ts +59 -11
  499. package/scripts/rules/rule-value.ts +17 -4
  500. package/scripts/rules/run-tail-rule.ts +1 -1
  501. package/scripts/rules/sed-in-place.ts +196 -0
  502. package/scripts/rules/shell-segments.ts +10 -2
  503. package/scripts/rules/stop-rule-rows.ts +1 -13
  504. package/scripts/rules/stop-rules.ts +33 -58
  505. package/scripts/rules/test-declared-commit.ts +1 -1
  506. package/scripts/rules/third-party-write.ts +110 -23
  507. package/scripts/rules/timed-rerun.ts +1 -1
  508. package/scripts/rules/turn-filings.ts +90 -0
  509. package/scripts/rules/worktree-guard.ts +22 -7
  510. package/scripts/run/board/run-board-ending-reason.ts +6 -2
  511. package/scripts/run/board/run-board-fit.ts +24 -1
  512. package/scripts/run/board/run-board-found.ts +134 -0
  513. package/scripts/run/board/run-board-header.ts +8 -0
  514. package/scripts/run/board/run-board-labels.ts +11 -1
  515. package/scripts/run/board/run-board-lane-time.ts +1 -0
  516. package/scripts/run/board/run-board-link.ts +15 -2
  517. package/scripts/run/board/run-board-notes.ts +7 -1
  518. package/scripts/run/board/run-board-operation.ts +4 -2
  519. package/scripts/run/board/run-board-phase.ts +2 -5
  520. package/scripts/run/board/run-board-plan.ts +1 -1
  521. package/scripts/run/board/run-board-read.ts +46 -1
  522. package/scripts/run/board/run-board-reason.ts +20 -8
  523. package/scripts/run/board/run-board-render-checks.ts +3 -2
  524. package/scripts/run/board/run-board-render-drawn.ts +11 -0
  525. package/scripts/run/board/run-board-render-found.ts +157 -0
  526. package/scripts/run/board/run-board-render-lead.ts +9 -0
  527. package/scripts/run/board/run-board-render-legend.ts +19 -0
  528. package/scripts/run/board/run-board-render-tail.ts +1 -1
  529. package/scripts/run/board/run-board-render.ts +82 -18
  530. package/scripts/run/board/run-board-spin.ts +1 -3
  531. package/scripts/run/board/run-board-status.ts +2 -0
  532. package/scripts/run/board/run-board-tick.ts +39 -10
  533. package/scripts/run/carry/run-carry-ended.ts +3 -2
  534. package/scripts/run/carry/run-carry.ts +3 -2
  535. package/scripts/run/cut/run-cut-cli.ts +33 -133
  536. package/scripts/run/cut/run-cut-digest.ts +152 -0
  537. package/scripts/run/cut/run-cut-handoff.ts +52 -8
  538. package/scripts/run/cut/run-cut-report.ts +24 -3
  539. package/scripts/run/cut/run-cut-resume.ts +129 -0
  540. package/scripts/run/cut/run-cut-review.ts +105 -0
  541. package/scripts/run/cut/run-cut.ts +18 -7
  542. package/scripts/run/detached-launch.ts +1 -1
  543. package/scripts/run/entry/run-entry-cli.ts +88 -19
  544. package/scripts/run/entry/run-entry-spill.ts +83 -0
  545. package/scripts/run/entry/run-entry-stop.ts +5 -1
  546. package/scripts/run/entry/run-entry-trust.ts +91 -0
  547. package/scripts/run/event/run-event-baseline.ts +56 -0
  548. package/scripts/run/event/run-event-filed.ts +9 -3
  549. package/scripts/run/event/run-event-overrun.ts +197 -0
  550. package/scripts/run/event/run-event-render.ts +4 -0
  551. package/scripts/run/event/run-event-stream.ts +25 -3
  552. package/scripts/run/hold/run-stop-notify.ts +3 -8
  553. package/scripts/run/merge/run-merge-cap.ts +57 -0
  554. package/scripts/run/merge/run-merge-cli.ts +66 -23
  555. package/scripts/run/merge/run-merge-outcome.ts +2 -0
  556. package/scripts/run/merge/run-merge-ship.ts +13 -3
  557. package/scripts/run/merge/run-merge-signals.ts +40 -23
  558. package/scripts/run/merge/run-merge-steps.ts +56 -36
  559. package/scripts/run/merge/run-merge-usage-limit.ts +53 -0
  560. package/scripts/run/merge/run-merge-withdrawn.ts +53 -0
  561. package/scripts/run/merge/run-merge.ts +41 -14
  562. package/scripts/run/review/run-review-cli.ts +306 -20
  563. package/scripts/run/review/run-review-early-push.ts +121 -0
  564. package/scripts/run/review/run-review-join-read.ts +94 -0
  565. package/scripts/run/review/run-review-pinned-accepted.ts +126 -0
  566. package/scripts/run/review/run-review-pinned-join.ts +216 -0
  567. package/scripts/run/review/run-review-pinned-steps.ts +383 -0
  568. package/scripts/run/review/run-review-pinned-tree.ts +37 -0
  569. package/scripts/run/review/run-review-pinned.ts +53 -0
  570. package/scripts/run/review/run-review-steps.ts +70 -34
  571. package/scripts/run/review/run-review.ts +26 -6
  572. package/scripts/run/run-parent-cut-hook.ts +3 -4
  573. package/scripts/run/run-prep.ts +1 -0
  574. package/scripts/run/run-stage-read.ts +2 -6
  575. package/scripts/run/run-step-cli.ts +17 -5
  576. package/scripts/run/run-step-lane-plan.ts +27 -0
  577. package/scripts/run/run-step-section.ts +26 -7
  578. package/scripts/run/run-step.ts +5 -2
  579. package/scripts/run/ship/run-ship-args.ts +19 -0
  580. package/scripts/run/ship/run-ship-brokered-review.ts +106 -0
  581. package/scripts/run/ship/run-ship-cli.ts +131 -39
  582. package/scripts/run/ship/run-ship-conflict-probe.ts +59 -0
  583. package/scripts/run/ship/run-ship-detach.ts +10 -4
  584. package/scripts/run/ship/run-ship-handover.ts +106 -0
  585. package/scripts/run/ship/run-ship-infra-retry.ts +16 -1
  586. package/scripts/run/ship/run-ship-lap.ts +63 -0
  587. package/scripts/run/ship/run-ship-next.ts +27 -26
  588. package/scripts/run/ship/run-ship-overlap.ts +8 -3
  589. package/scripts/run/ship/run-ship-place.ts +9 -2
  590. package/scripts/run/ship/run-ship-planned.ts +92 -0
  591. package/scripts/run/ship/run-ship-pre-detach-pinned.ts +197 -0
  592. package/scripts/run/ship/run-ship-pre-detach-timed.ts +7 -8
  593. package/scripts/run/ship/run-ship-pre-detach.ts +77 -23
  594. package/scripts/run/ship/run-ship-preflight.ts +16 -4
  595. package/scripts/run/ship/run-ship-probe.ts +23 -9
  596. package/scripts/run/ship/run-ship-relaunch-cap.ts +43 -7
  597. package/scripts/run/ship/run-ship-relaunch.ts +51 -0
  598. package/scripts/run/ship/run-ship-return.ts +103 -29
  599. package/scripts/run/ship/run-ship-review-reason.ts +24 -0
  600. package/scripts/run/ship/run-ship-review-steps.ts +230 -59
  601. package/scripts/run/ship/run-ship-review.ts +171 -51
  602. package/scripts/run/ship/run-ship-reviewer.ts +138 -42
  603. package/scripts/run/ship/run-ship-scoped.ts +39 -10
  604. package/scripts/run/ship/run-ship-stage.ts +49 -13
  605. package/scripts/run/ship/run-ship-steps.ts +24 -4
  606. package/scripts/run/ship/run-ship-stop-cause.ts +43 -2
  607. package/scripts/run/ship/run-ship-stop-context.ts +50 -13
  608. package/scripts/run/ship/run-ship-sync.ts +171 -30
  609. package/scripts/run/ship/run-ship-synced-scope.ts +178 -0
  610. package/scripts/run/ship/run-ship-wait-cli.ts +145 -0
  611. package/scripts/run/ship/run-ship-waiter.ts +114 -0
  612. package/scripts/run/ship/run-ship.ts +16 -2
  613. package/scripts/run/wake/run-wake-caffeinate.ts +48 -0
  614. package/scripts/run/wake/run-wake-cli.ts +6 -113
  615. package/scripts/run/wake/run-wake-code.ts +53 -11
  616. package/scripts/run/wake/run-wake-driver.ts +5 -3
  617. package/scripts/run/wake/run-wake-finish.ts +137 -0
  618. package/scripts/run/wake/run-wake-loop.ts +5 -3
  619. package/scripts/run/wake/run-wake-ports.ts +11 -3
  620. package/scripts/run/wake/run-wake-session.ts +2 -0
  621. package/scripts/run/wake/run-wake.ts +3 -2
  622. package/scripts/safe-chain/ci-installer-pin.ts +49 -8
  623. package/scripts/safe-chain/project-config.ts +3 -2
  624. package/scripts/security/secretlint-scan.ts +42 -9
  625. package/scripts/security/security-audit-provision-main.ts +9 -1
  626. package/scripts/security/security-audit-provision.ts +1 -1
  627. package/scripts/security/security-audit.ts +21 -1
  628. package/scripts/security/sonar-file.ts +10 -5
  629. package/scripts/split/split-assess.ts +12 -3
  630. package/scripts/split/split-early-notice.ts +116 -0
  631. package/scripts/split/split-measure.ts +20 -2
  632. package/scripts/split/split-resolution.ts +107 -0
  633. package/scripts/test/direct-importers.ts +185 -0
  634. package/scripts/test/pure-files-entries.ts +83 -0
  635. package/scripts/test/related-heavy-tests.ts +81 -0
  636. package/scripts/test/test-no-base.ts +88 -0
  637. package/scripts/test/test-pinned-cache-logic.ts +363 -0
  638. package/scripts/test/test-pinned-cache.ts +337 -0
  639. package/scripts/test/test-pinned-emit.ts +49 -0
  640. package/scripts/test/test-pinned-functions.ts +116 -0
  641. package/scripts/test/test-pinned-ledger.ts +80 -0
  642. package/scripts/test/test-pinned-logic.ts +208 -20
  643. package/scripts/test/test-pinned-main.ts +84 -18
  644. package/scripts/test/test-pinned-pool.ts +73 -0
  645. package/scripts/test/test-pinned-probe.ts +254 -0
  646. package/scripts/test/test-pinned-prune-logic.ts +118 -0
  647. package/scripts/test/test-pinned-reach.ts +194 -0
  648. package/scripts/test/test-pinned-record.ts +196 -0
  649. package/scripts/test/test-pinned-rewire.ts +148 -0
  650. package/scripts/test/test-pinned-spawn.ts +81 -0
  651. package/scripts/test/test-pinned-stage.ts +70 -0
  652. package/scripts/test/test-pinned.ts +258 -47
  653. package/scripts/test/test-reach-cli.ts +61 -0
  654. package/scripts/test/test-read-map.ts +35 -11
  655. package/scripts/test/test-read-recording.ts +17 -5
  656. package/scripts/test/test-read-trace.ts +26 -8
  657. package/scripts/test/test-red-logic.ts +46 -7
  658. package/scripts/test/test-red.ts +34 -14
  659. package/scripts/test/test-related-claim.ts +37 -0
  660. package/scripts/test/test-related-main.ts +4 -1
  661. package/scripts/test/test-related-moved.ts +91 -0
  662. package/scripts/test/test-related-record.ts +43 -23
  663. package/scripts/test/test-related-scope.ts +99 -34
  664. package/scripts/test/test-related-synced.ts +135 -0
  665. package/scripts/test/test-related-touched.ts +84 -0
  666. package/scripts/test/test-related.ts +167 -83
  667. package/scripts/test/test-unit-guard.ts +28 -5
  668. package/scripts/test/unit-durations.ts +41 -0
  669. package/scripts/test/unit-worker-share.ts +5 -4
  670. package/scripts/test/vitest-cache-directory.ts +19 -0
  671. package/scripts/test/vitest-cli-excludes.ts +14 -0
  672. package/scripts/test/vitest-failure-summary.ts +111 -0
  673. package/scripts/test/whole-suite-files.ts +12 -1
  674. package/scripts/time-runtime/time-batch-guard.ts +42 -4
  675. package/scripts/time-runtime/time-bundle-call.ts +1 -1
  676. package/scripts/time-runtime/time-bundles.ts +36 -5
  677. package/scripts/time-runtime/time-markers.ts +2 -2
  678. package/scripts/time-runtime/time-search-call.ts +38 -0
  679. package/scripts/time-runtime/time-shell.ts +1 -1
  680. package/scripts/time-runtime/time-transcript-line.ts +17 -5
  681. package/scripts/version/development-engines-verdict.ts +124 -0
  682. package/scripts/version/latest-corepack.ts +2 -0
  683. package/templates/gitignore +2 -0
  684. package/templates/workflows/ci.yml +41 -0
  685. package/dist/hooks/chunk-AZX4FGQ3.js +0 -207
  686. package/dist/hooks/chunk-PT5R6UJD.js +0 -7131
  687. package/dist/hooks/chunk-TUIFS5E7.js +0 -72
  688. package/dist/hooks/chunk-YCDOIR3I.js +0 -3456
  689. package/scripts/cost-runtime/context-cut-threshold.ts +0 -11
  690. package/scripts/rules/filing-cap.ts +0 -139
@@ -0,0 +1,110 @@
1
+ import { time_shell } from '#scripts/time-runtime/time-shell'
2
+ import { time_transcript_line } from '#scripts/time-runtime/time-transcript-line'
3
+ import { piped_verification } from './piped-verification'
4
+ import { rule_value } from './rule-value'
5
+ import { shell_segments } from './shell-segments'
6
+
7
+ // The `piped-verification` refusals named by the filter that drew them, for `josh rule:value --fresh`.
8
+ //
9
+ // **Which shape to move from the refusal to the rewrite is read here, not in a transcript by hand.**
10
+ // The refusal text is one fixed reason, so the row's `refused` count says how often and never why; the
11
+ // command that drew it is the call the errored result answers, paired by its `tool_use_id`
12
+ // (joshuafolkken/kit#5266). A shape is named by the first that holds, in the order below: the filters
13
+ // that hide a verdict first, the chain — a line of more than one command, a heredoc's body lines
14
+ // included — only where no filter explains the refusal.
15
+
16
+ const SHAPE = {
17
+ head: 'head',
18
+ grep: 'grep',
19
+ script: 'awk|sed|tee',
20
+ chain: 'chain',
21
+ other: 'other',
22
+ } as const
23
+
24
+ type RefusalShape = (typeof SHAPE)[keyof typeof SHAPE]
25
+ type ShapeCounts = Map<RefusalShape, number>
26
+
27
+ const HEAD_FILTER = /^head\b/u
28
+ const SCRIPT_FILTER = /^(?:awk|sed|tee)\b/u
29
+ // The reason's opening, matched as `rule-value.ts` matches a refusal; a text without the cheap mark —
30
+ // the reason's ASCII words, which the rewrite note does not share — is skipped unparsed.
31
+ const SIGNATURE = piped_verification.PIPED_VERIFICATION_REASON.slice(
32
+ 0,
33
+ rule_value.REASON_SIGNATURE_LENGTH,
34
+ )
35
+ const CHEAP_MARK = 'piped verification: a pipeline exits'
36
+ const HEADING = 'piped-verification refusals by shape:'
37
+ const SHAPE_GAP = ' · '
38
+
39
+ function filter_shape(filters: ReadonlyArray<string>): RefusalShape | undefined {
40
+ if (filters.some((piece) => HEAD_FILTER.test(piece))) return SHAPE.head
41
+ if (filters.some((piece) => piped_verification.EARLY_EXIT_GREP.test(piece))) return SHAPE.grep
42
+
43
+ return filters.some((piece) => SCRIPT_FILTER.test(piece)) ? SHAPE.script : undefined
44
+ }
45
+
46
+ function shape_of(command: string): RefusalShape {
47
+ const by_filter = filter_shape(piped_verification.check_filters(command))
48
+
49
+ if (by_filter !== undefined) return by_filter
50
+
51
+ const segments = shell_segments.segments_of(time_shell.unquoted(command))
52
+
53
+ return segments.length > 1 ? SHAPE.chain : SHAPE.other
54
+ }
55
+
56
+ interface Pairing {
57
+ commands: Map<string, string>
58
+ refused: Array<string>
59
+ }
60
+
61
+ type Block = NonNullable<ReturnType<typeof time_transcript_line.parse_line>>['blocks'][number]
62
+
63
+ function is_refusal(block: Block): boolean {
64
+ if (block.error_text === '') return false
65
+
66
+ return time_transcript_line.refusal_body(block.error_text).startsWith(SIGNATURE)
67
+ }
68
+
69
+ function pair_block(block: Block, pairing: Pairing): void {
70
+ if (block.id !== '') pairing.commands.set(block.id, time_shell.bash_command(block.input))
71
+ if (is_refusal(block)) pairing.refused.push(block.result_id)
72
+ }
73
+
74
+ function blocks_of(text: string): Array<Block> {
75
+ return text.split('\n').flatMap((line) => time_transcript_line.parse_line(line)?.blocks ?? [])
76
+ }
77
+
78
+ // The commands one transcript's `piped-verification` refusals answered.
79
+ function refused_commands(text: string): Array<string> {
80
+ if (!text.includes(CHEAP_MARK)) return []
81
+
82
+ const pairing: Pairing = { commands: new Map(), refused: [] }
83
+
84
+ for (const block of blocks_of(text)) pair_block(block, pairing)
85
+
86
+ return pairing.refused
87
+ .map((id) => pairing.commands.get(id) ?? '')
88
+ .filter((command) => command !== '')
89
+ }
90
+
91
+ function count(text: string, counts: ShapeCounts): void {
92
+ for (const command of refused_commands(text)) {
93
+ const shape = shape_of(command)
94
+
95
+ counts.set(shape, (counts.get(shape) ?? 0) + 1)
96
+ }
97
+ }
98
+
99
+ function render(counts: ShapeCounts): string {
100
+ const cells = Object.values(SHAPE).map(
101
+ (shape) => `${shape} ${(counts.get(shape) ?? 0).toString()}`,
102
+ )
103
+
104
+ return `${HEADING} ${cells.join(SHAPE_GAP)}`
105
+ }
106
+
107
+ const piped_verification_shapes = { HEADING, SHAPE, count, refused_commands, render, shape_of }
108
+
109
+ export { piped_verification_shapes }
110
+ export type { RefusalShape, ShapeCounts }
@@ -40,6 +40,7 @@ import { shell_segments } from './shell-segments'
40
40
  // change. The Issue that filed this rule names `eval` for that reason.
41
41
  const VERIFICATION_COMMANDS: ReadonlySet<string> = new Set([
42
42
  'check',
43
+ 'check:related',
43
44
  'cspell',
44
45
  'cspell:dot',
45
46
  'eval',
@@ -97,6 +98,13 @@ function pipelines_of(command: string): Array<Array<string>> {
97
98
  .map((segment) => segment.split('|').map((piece) => piece.trim()))
98
99
  }
99
100
 
101
+ // The filters behind each check on the line — what a refusal is named by (`piped-verification-shapes.ts`).
102
+ function check_filters(command: string): Array<string> {
103
+ return pipelines_of(command).flatMap(([head = '', ...filters]) =>
104
+ is_verification_command(head) ? filters : [],
105
+ )
106
+ }
107
+
100
108
  function command_pieces(command: string): Array<string> {
101
109
  return pipelines_of(command).flat()
102
110
  }
@@ -125,7 +133,12 @@ function keeps_verdict_intact(command: string): boolean {
125
133
  // `grep` stopping at its first match — closes the pipe on a check still writing, and under `pipefail` the
126
134
  // check's SIGPIPE turns a passing run into exit 141; `head` also cuts the verdict line josh prints last.
127
135
  // `cut`, `sort`, `uniq` and `wc` read to the end as well, and a `grep … | cut -c1-250` narrowing was the
128
- // commonest masking still refused after #3875 (joshuafolkken/kit#3920).
136
+ // commonest masking still refused after #3875 (joshuafolkken/kit#3920). `tee` passes every line on, and an
137
+ // `awk` or `sed` script reads to the end where it can neither exit nor reach past the pipe — read on the
138
+ // typed piece, since the script is quoted: an `awk` program with no `exit`, `nextfile`, `system`, `getline` or `|`,
139
+ // and a `sed` script of `p`, `d` and `s///` alone (joshuafolkken/kit#5266). A quoted `<`, `>` or `&`
140
+ // still refuses below: inside `awk` a `>` may be `print > "f"`, which no reading here tells from a
141
+ // comparison.
129
142
  //
130
143
  // **Each masked check is wrapped on its own, and the rest of the line runs as typed.** Most of the
131
144
  // refusals left after #3570 were a check chained beside other work — `vitest …; pnpm josh lint:related
@@ -143,9 +156,18 @@ function keeps_verdict_intact(command: string): boolean {
143
156
  const PIPEFAIL_OPEN = '(set -o pipefail; '
144
157
  const PIPEFAIL_CLOSE = ')'
145
158
  const COMMENT_START = /(?:^|\s)#/u
146
- const FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc)\b/u
159
+ const FULL_READING_FILTER = /^(?:tail|grep|cut|sort|uniq|wc|tee)(?=\s|$)/u
147
160
  const EARLY_EXIT_GREP =
148
161
  /^grep\b.*\s(?:-[a-zA-Z]*[lLmq]|--(?:quiet|silent|max-count|files-with(?:out)?-match))/u
162
+ const AWK_PROGRAM = /^awk\s+(?:-F\s*\S+\s+)?'[^']*'$/u
163
+ const AWK_EARLY_EXIT = /\b(?:exit|nextfile|system|getline)\b|\|/u
164
+ const SED_TEXT = String.raw`(?:[^/\\']|\\.)*`
165
+ const SED_ADDRESS = String.raw`(?:\d+|\$|/${SED_TEXT}/)`
166
+ const SED_COMMAND = String.raw`(?:${SED_ADDRESS}(?:,${SED_ADDRESS})?)?!?(?:p|d|s/${SED_TEXT}/${SED_TEXT}/[gip\d]*)`
167
+ const SED_PROGRAM = new RegExp(
168
+ String.raw`^sed\s+(?:-[nEr]+\s+)*'${SED_COMMAND}(?:;\s*${SED_COMMAND})*'$`,
169
+ 'u',
170
+ )
149
171
 
150
172
  const ALLOWED_OPERATORS = /2>&1|&&/gu
151
173
  const SIDE_EFFECT_SYNTAX = /[&<>`]|\$\(/u
@@ -156,14 +178,40 @@ const PIPEFAIL_NOTE =
156
178
  "`(set -o pipefail; …)` to carry the check's status, and the rest of the line ran as typed. Read " +
157
179
  'the printed verdict, never the exit code alone — `prompts/collaboration-workflow/output-bounds.md`.'
158
180
 
159
- function reads_to_the_end(piece: string): boolean {
160
- return FULL_READING_FILTER.test(piece) && !EARLY_EXIT_GREP.test(piece)
181
+ function is_full_reading_script(typed: string): boolean {
182
+ if (AWK_PROGRAM.test(typed)) return !AWK_EARLY_EXIT.test(typed)
183
+
184
+ return SED_PROGRAM.test(typed)
161
185
  }
162
186
 
163
- function is_filtered_check(pipeline: Array<string>): boolean {
164
- const [head = '', ...filters] = pipeline
187
+ // A filter's name and flags are read on the blanked piece, its quoted script on the typed one.
188
+ function reads_to_the_end(piece: string, typed: string): boolean {
189
+ if (FULL_READING_FILTER.test(piece)) return !EARLY_EXIT_GREP.test(piece)
165
190
 
166
- return is_verification_command(head) && filters.every((piece) => reads_to_the_end(piece))
191
+ return is_full_reading_script(typed)
192
+ }
193
+
194
+ // The typed text at the offsets of the blanked pieces, each `gap` characters apart — the blanked line
195
+ // keeps every offset, so a cut found there is the same cut in the typed one.
196
+ function typed_slices(raw: string, pieces: ReadonlyArray<string>, gap: number): Array<string> {
197
+ const offset = { at: 0 }
198
+
199
+ return pieces.map((piece) => {
200
+ const typed = raw.slice(offset.at, offset.at + piece.length)
201
+
202
+ offset.at += piece.length + gap
203
+
204
+ return typed
205
+ })
206
+ }
207
+
208
+ function is_filtered_check(pieces: ReadonlyArray<string>, typed: ReadonlyArray<string>): boolean {
209
+ const [head = '', ...filters] = pieces.map((piece) => piece.trim())
210
+
211
+ return (
212
+ is_verification_command(head) &&
213
+ filters.every((piece, index) => reads_to_the_end(piece, typed[index + 1]?.trim() ?? ''))
214
+ )
167
215
  }
168
216
 
169
217
  function has_side_effect_syntax(command: string): boolean {
@@ -173,9 +221,13 @@ function has_side_effect_syntax(command: string): boolean {
173
221
  // The side-effect syntax is read on the typed part, since a double-quoted `$(cmd)` still runs; the pipe
174
222
  // cut and the grouping are read on the blanked one, since a quoted `|` or `(` is text.
175
223
  function is_rewritable_part(raw: string, judged: string): boolean {
176
- const pipeline = judged.split('|').map((piece) => piece.trim())
224
+ const pieces = judged.split('|')
177
225
 
178
- return !has_side_effect_syntax(raw) && !GROUP_SYNTAX.test(judged) && is_filtered_check(pipeline)
226
+ return (
227
+ !has_side_effect_syntax(raw) &&
228
+ !GROUP_SYNTAX.test(judged) &&
229
+ is_filtered_check(pieces, typed_slices(raw, pieces, 1))
230
+ )
179
231
  }
180
232
 
181
233
  // Where the part's code ends: an unquoted `#` opens a comment, which would swallow the closing `)`.
@@ -210,14 +262,10 @@ function pipefail_command(command: string): string | undefined {
210
262
 
211
263
  if (judged === undefined || !is_masked_verification(command)) return undefined
212
264
 
213
- const offset = { at: 0 }
214
- const parts = shell_segments.parts_of(judged).map((part) => {
215
- const raw = command.slice(offset.at, offset.at + part.length)
216
-
217
- offset.at += part.length
218
-
219
- return rewritten_part(raw, part)
220
- })
265
+ const pieces = shell_segments.parts_of(judged)
266
+ const parts = typed_slices(command, pieces, 0).map((raw, index) =>
267
+ rewritten_part(raw, pieces[index] ?? ''),
268
+ )
221
269
 
222
270
  const rewritten = parts.includes(undefined) ? undefined : parts.join('')
223
271
 
@@ -236,9 +284,11 @@ function pipefail_input(call: GuardedCall): Record<string, unknown> | undefined
236
284
  }
237
285
 
238
286
  const piped_verification = {
287
+ EARLY_EXIT_GREP,
239
288
  PIPED_VERIFICATION_REASON,
240
289
  PIPEFAIL_NOTE,
241
290
  VERIFICATION_COMMANDS,
291
+ check_filters,
242
292
  is_masked_verification,
243
293
  keeps_verdict_intact,
244
294
  pipefail_input,
@@ -1,5 +1,7 @@
1
1
  import { time_shell } from '#scripts/time-runtime/time-shell'
2
2
  import { bash_triggers } from './bash-triggers'
3
+ import { early_heartbeat } from './early-heartbeat'
4
+ import { shell_segments } from './shell-segments'
3
5
 
4
6
  // The trigger and the delivered text behind the `poll-loop` row of `delivered-rules.ts`.
5
7
  // The hand-written wait loop a lane child improvises over a backgrounded
@@ -23,8 +25,7 @@ import { bash_triggers } from './bash-triggers'
23
25
  // back on the run's self-restraint — the thing that let these loops through in the first place.
24
26
 
25
27
  // A `while` or `until` header, in either spelling a poll loop reaches for. The trailing whitespace is
26
- // what separates the keyword from a path that merely begins with it (`until.txt`); a `for` loop is out
27
- // by omission — it iterates a fixed list rather than probing a state.
28
+ // what separates the keyword from a path that merely begins with it (`until.txt`).
28
29
  const LOOP_KEYWORD = /\b(?:while|until)\s/u
29
30
  // A `sleep` command with any argument — the pause between probes that makes a loop a poll rather than
30
31
  // a stream reader. Read anywhere in the line rather than at a command position, because the loop body
@@ -52,10 +53,72 @@ const PROBE_HEADERS: ReadonlyArray<RegExp> = [
52
53
  String.raw`grep\s+-[A-Za-z]*q`,
53
54
  ].map((probe) => new RegExp(`${WHILE_HEADER}${probe}`, 'u'))
54
55
 
56
+ // **A counted `for` that sleeps is a poll with a deadline** (joshuafolkken/kit#4800). A lane child
57
+ // waited out a backgrounded reviewer with `for i in $(seq 1 30); do sleep 10; done`, and the
58
+ // completion notification sat queued until the loop returned — the same held-back exit a `while` poll
59
+ // costs, spelled as a count. A header that counts — `$(seq …)`, a `{1..N}` range, or `((…))` — has no
60
+ // list to work through, so its `sleep` is the whole point. A `for` over a real list (`for f in *.ts`)
61
+ // is ordinary work and passes, sleep or not. The `sleep` is read in the loop's body only — from the
62
+ // header to its first `done` — so a counted loop beside an unrelated `sleep` is not a wait (review
63
+ // round 1); a nested loop's inner `done` cuts the body short, which under-refuses and so fails safe.
64
+ const COUNTED_FOR = new RegExp(
65
+ String.raw`${COMMAND_POSITION}for(?:\s*\(\(|\s+\w+\s+in\s+(?:\$\(\s*seq\b|\{\d+\.\.))`,
66
+ 'u',
67
+ )
68
+ const THROUGH_FIRST_DONE = /^[\s\S]*?\bdone\b/u
69
+
70
+ // **A sleep chain in front of a read is the same poll, unrolled** (joshuafolkken/kit#5115). A lane
71
+ // child watched a backgrounded task with `sleep 20; sleep 20; sleep 20; tail -c 1500 …/x.output` and
72
+ // `sleep 20; ps -p 123`: no loop keyword, so the rows above miss it, and a read segment makes it
73
+ // work rather than a bare timer, so `early-heartbeat` stands down. Its sleep is only there for the
74
+ // read after it, which the completion notification makes unnecessary. The call is cut where the shell
75
+ // starts a program (`shell_segments.commands_of`), and every cut is a sleep, an inert marker
76
+ // (`early-heartbeat`'s own reading of both), or a read of a file or a process — at least one read,
77
+ // which is what keeps it disjoint from `early-heartbeat`'s bare timer. A short settle (`sleep 3; tail`)
78
+ // is below the threshold and passes.
79
+ const SETTLE_SECONDS = 10
80
+ const NO_SECONDS = 0
81
+ const READ_COMMAND = /^(?:tail|cat|head|ps|stat|grep|wc|ls|pgrep|test|kill\s+-0|\[\[?)(?=\s|$)/u
82
+
83
+ function slept_seconds(segments: ReadonlyArray<string>): number {
84
+ return segments.reduce(
85
+ (total, segment) => total + (early_heartbeat.sleep_seconds(segment) ?? NO_SECONDS),
86
+ NO_SECONDS,
87
+ )
88
+ }
89
+
90
+ // Only the sleep in front of the first read counts: `tail x; sleep 20` reads first and waits after,
91
+ // which is not a wait *for* the read.
92
+ function is_sleep_then_read(plain: string): boolean {
93
+ const segments = shell_segments.commands_of(plain).filter((segment) => segment !== '')
94
+ const first_work = segments.findIndex((segment) => !early_heartbeat.is_wait_or_inert(segment))
95
+ const waited = first_work === -1 ? NO_SECONDS : slept_seconds(segments.slice(0, first_work))
96
+
97
+ if (waited < SETTLE_SECONDS) return false
98
+
99
+ return segments
100
+ .slice(first_work)
101
+ .every((segment) => early_heartbeat.is_wait_or_inert(segment) || READ_COMMAND.test(segment))
102
+ }
103
+
55
104
  function is_state_wait(plain: string): boolean {
56
105
  return UNTIL_HEADER.test(plain) || PROBE_HEADERS.some((header) => header.test(plain))
57
106
  }
58
107
 
108
+ function is_counted_sleep(plain: string): boolean {
109
+ const header = COUNTED_FOR.exec(plain)
110
+
111
+ if (header === null) return false
112
+
113
+ const rest = plain.slice(header.index)
114
+
115
+ return SLEEP_COMMAND.test(THROUGH_FIRST_DONE.exec(rest)?.[0] ?? rest)
116
+ }
117
+
118
+ function is_sleeping_loop(plain: string): boolean {
119
+ return (LOOP_KEYWORD.test(plain) && SLEEP_COMMAND.test(plain)) || is_counted_sleep(plain)
120
+ }
121
+
59
122
  // **A `while read … done < file` is a stream reader, not a poll**, even when its body sleeps to
60
123
  // throttle processing. Its condition consumes input rather
61
124
  // than probing for a state — a poll never reads its condition, it *tests* it (grep, wc, a file check)
@@ -80,18 +143,18 @@ function is_poll_loop(command: string): boolean {
80
143
  if (is_stream_reader(plain)) return false
81
144
  if (is_state_wait(plain)) return true
82
145
 
83
- return LOOP_KEYWORD.test(plain) && SLEEP_COMMAND.test(plain)
146
+ return is_sleeping_loop(plain) || is_sleep_then_read(plain)
84
147
  }
85
148
 
86
149
  // The instruction in the shape a refusal can carry: why the command's exit is the thing to wait on,
87
150
  // and the one route that delivers that exit. The measurement behind it is in
88
151
  // `docs/maintainers/rule-delivery-rationale.md`.
89
152
  const POLL_LOOP_REASON =
90
- "⛔ output poll loop: this hand-writes a `while`/`until` wait loop over a command's output, a " +
91
- 'marker file or a process, and the loop does not know when the command finished. Issue the long ' +
92
- 'command with `run_in_background` set and end the turn — its exit re-invokes you; for CI, ' +
93
- '`pnpm josh followup` (`.claude/skills/workflow-commands/background-commands.md`). It fires on ' +
94
- 'every occurrence.'
153
+ '⛔ output poll loop: a `while`/`until` or counted `for` loop, or a `sleep` chain before a read, ' +
154
+ 'does not know when the task finished. `run_in_background` and end the turn — its exit ' +
155
+ 're-invokes you; a background `Agent` is waited on by its completion notification (lane child: ' +
156
+ 'bare `sleep 20`, `pnpm josh run:liveness`); CI by `pnpm josh followup` ' +
157
+ '(`background-commands.md`). Fires on every occurrence.'
95
158
 
96
159
  // The row itself, so `delivered-rules.ts` spreads one entry. `decide` returns true so it refuses every
97
160
  // occurrence; it declares no `keeps` — waiting on the task exit is the *absence* of a loop, not a
@@ -106,7 +169,7 @@ const ROW = {
106
169
  title: 'Output poll loop',
107
170
  topic: '`.claude/skills/workflow-commands/background-commands.md`',
108
171
  fires:
109
- "a hand-written `while` / `until` wait loop over a command's output, a marker file or a process. Every occurrence",
172
+ "a hand-written `while` / `until` wait loop over a command's output, a marker file or a process, a counted `for` that sleeps, or a `sleep` chain of 10 s or more before a read of a file or process. Every occurrence",
110
173
  quiet: 'the command started with `run_in_background` — its exit ends the wait',
111
174
  },
112
175
  }
@@ -99,9 +99,9 @@ function is_pr_merge(command: string): boolean {
99
99
  const PR_MERGE_REASON =
100
100
  '⛔ direct PR merge: `gh pr merge`, a `gh api` / `curl` call to `…/pulls/<N>/merge`, a ' +
101
101
  '`mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file ' +
102
- 'merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup` when the ' +
103
- 'run authorizes it; otherwise leave it to the user (`CLAUDE.md` → Git Rules). This rule fires on ' +
104
- 'every occurrence.'
102
+ 'merges outside the verified route, and a merge is Tier C. Merge with `pnpm josh followup` if the ' +
103
+ 'run authorizes it; otherwise leave it to the user (`operating-rules.md` → git-rules). This rule ' +
104
+ 'fires on every occurrence.'
105
105
 
106
106
  const ROW = {
107
107
  id: 'pr-merge',
@@ -110,7 +110,7 @@ const ROW = {
110
110
  decide: (): boolean => true,
111
111
  about: {
112
112
  title: 'Direct PR merge',
113
- topic: '`CLAUDE.md` → Git Rules',
113
+ topic: '`operating-rules.md` → "git-rules"',
114
114
  fires:
115
115
  '`gh pr merge`, a `gh api` / `curl` call to `…/pulls/<N>/merge`, a `mergePullRequest` / `enablePullRequestAutoMerge` mutation, or a GraphQL body read from a file. Every occurrence',
116
116
  quiet:
@@ -118,6 +118,6 @@ const ROW = {
118
118
  },
119
119
  }
120
120
 
121
- const pr_merge = { PR_MERGE_REASON, ROW, is_pr_merge }
121
+ const pr_merge = { PR_MERGE_REASON, ROW, is_graphql_call, is_pr_merge }
122
122
 
123
123
  export { pr_merge }
@@ -139,7 +139,7 @@ function uncut_lane_issue(state: LaneCutState): string | undefined {
139
139
  }
140
140
 
141
141
  // **The cut is taken only when the current context is worth its resume**.
142
- // Below the shared `CONTEXT_CUT_THRESHOLD` a short lane has no accumulation a cut would drop, so the
142
+ // Below the model-priced `pnpm josh cost --cut` threshold a short lane has no accumulation a cut would drop, so the
143
143
  // gate runs uncut rather than paying for a relaunch; an unmeasurable session keeps the old
144
144
  // unconditional cut as the safety net. The verdict is
145
145
  // `cost_cli.session_verdict`'s, the same statistic and threshold `run:cut` and the implementation-phase
@@ -163,8 +163,8 @@ function is_uncut_gate(command: string, state: LaneCutState = current_state()):
163
163
  const PRE_GATE_CUT_REASON =
164
164
  '⛔ pre-gate cut: this checkout is a lane dispatched for this issue and the gate is the boundary to ' +
165
165
  'cut at. Issue `pnpm josh run:cut <N>`: on `cut`, end the turn immediately — no Telegram is owed; ' +
166
- 'on any other verdict (`not-a-lane`, `busy`, …), reissue the gate — this fires once per run ' +
167
- '(`.claude/skills/workflow-commands/pre-gate-cut.md`).'
166
+ 'on `split-unresolved`, answer its notice and rerun it; on any other verdict (`not-a-lane`, `busy`, …), ' +
167
+ 'reissue the gate — this fires once per run (`.claude/skills/workflow-commands/pre-gate-cut.md`).'
168
168
 
169
169
  // **The pre-gate cut is two acts, one call apart**, so its `rule:value`
170
170
  // denominator is only the runs that reached the boundary: a run that issues the cut's entry check and,
@@ -2,7 +2,9 @@ import path from 'node:path'
2
2
  import { doctor_consumer } from '#scripts/doctor/doctor-consumer'
3
3
  import { json_value } from '#scripts/lib/json-value'
4
4
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
5
+ import { time_shell } from '#scripts/time-runtime/time-shell'
5
6
  import { bash_triggers } from './bash-triggers'
7
+ import { edit_tool } from './edit-tool'
6
8
  import { git_argv } from './git-argv'
7
9
  import { shell_segments } from './shell-segments'
8
10
 
@@ -22,7 +24,7 @@ import { shell_segments } from './shell-segments'
22
24
  // It fires on every occurrence, for the reason `git-force.ts` does.
23
25
 
24
26
  const READ_TOOL = 'Read'
25
- const EDIT_TOOLS: ReadonlySet<string> = new Set(['Edit', 'Write'])
27
+ const { is_edit_tool } = edit_tool
26
28
  const SECRET_FILES: ReadonlySet<string> = new Set(['.env', '.dev.vars'])
27
29
  const ENVIRONMENT_VARIANT_PREFIX = '.env.'
28
30
  // The two variants `.gitignore` un-ignores: they are tracked and hold placeholders, not secrets. The
@@ -31,10 +33,22 @@ const TRACKED_VARIANTS: ReadonlySet<string> = new Set(['.env.example', '.env.tes
31
33
  const SETTINGS_FILE = path.join('.claude', 'settings.json')
32
34
  const WHITESPACE = /\s+/u
33
35
  const TOKEN_QUOTES = /["']/gu
34
- const INPUT_REDIRECT = '<'
35
- // Programs that print a named file's bytes, or run them. `cp` and `mv` are deliberately absent: they
36
- // write rather than print, and `cp .env.example .env` is how a checkout's `.env` is made.
37
- const SHELL_READERS: ReadonlySet<string> = new Set([
36
+ // Each character of a redirect operator (`<`, `>`, `>>`, `2>`, `&>`) — and the clobber `>|` whole —
37
+ // is spaced out to a mark, so the word after the last one is the file it reads or writes.
38
+ const REDIRECT = />\||[<>]/gu
39
+ const REDIRECT_MARK = '<'
40
+ // Programs that run their quoted argument as a script.
41
+ const SHELLS: ReadonlySet<string> = new Set(['bash', 'eval', 'sh', 'zsh'])
42
+ // The body of a `$(…)` (innermost) or a backtick command substitution.
43
+ const SUBSTITUTION = /\$\((?<dollar>[^()]*)\)|`(?<backtick>[^`]*)`/gu
44
+ // Programs that print a named file's bytes or run them, and programs that write one: a write could set
45
+ // a guard switch, so `cp .env.example .env` is refused too (kit #4660) — the person makes the file.
46
+ const SHELL_PROGRAMS: ReadonlySet<string> = new Set([
47
+ 'cp',
48
+ 'install',
49
+ 'ln',
50
+ 'mv',
51
+ 'tee',
38
52
  '.',
39
53
  'awk',
40
54
  'base64',
@@ -88,33 +102,82 @@ function is_environment_path(token: string): boolean {
88
102
  return SECRET_FILES.has(name) || name.startsWith(ENVIRONMENT_VARIANT_PREFIX)
89
103
  }
90
104
 
91
- // A shell read reaches the same bytes a `Read` does, so a segment that runs a reader (`cat .env`,
92
- // `source .env`) or redirects the file into a command (`< .env`) is refused too. Claude Code applies
93
- // the `Read` deny rules to the readers it recognizes, but `source .env.local` passes them (measured on
94
- // 2.1.289, kit #3602), so the variants are matched here as well.
95
- function is_environment_read_segment(segment: string): boolean {
96
- const words = segment
97
- .replaceAll(INPUT_REDIRECT, ' < ')
105
+ // A shell read reaches the same bytes a `Read` does, and a shell write changes the file an `Edit`
106
+ // would, so a segment that runs one of the programs (`cat .env`, `source .env`, `tee .env`) or
107
+ // redirects the file (`< .env`, `>> .env`) is refused too. Claude Code applies the `Read` deny rules to
108
+ // the readers it recognizes, but `source .env.local` passes them (measured on 2.1.289, kit #3602), so
109
+ // the variants are matched here as well.
110
+ //
111
+ // A quoted `>` is text (`git commit -m "x >> .env"`), so the marks are read on the quote-blanked
112
+ // segment, whose offsets match the original; a shell's quoted script (`sh -c "echo x > .env"`) runs,
113
+ // so its marks are read as written, as is a heredoc line, which `blanked` does not cut.
114
+ function redirect_marks_of(segment: string, program: string | undefined): string {
115
+ if (program !== undefined && SHELLS.has(program)) return segment
116
+
117
+ return time_shell.blanked(segment) ?? segment
118
+ }
119
+
120
+ function spaced_redirects(segment: string, program: string | undefined): string {
121
+ const marks = redirect_marks_of(segment, program)
122
+
123
+ return segment.replaceAll(REDIRECT, (mark, offset: number) =>
124
+ marks.startsWith(mark, offset) ? ` ${REDIRECT_MARK} ` : mark,
125
+ )
126
+ }
127
+
128
+ function is_environment_shell_segment(segment: string): boolean {
129
+ const call = git_argv.program_of(segment)
130
+ const words = spaced_redirects(segment, call?.name)
98
131
  .split(WHITESPACE)
99
132
  .filter((word) => word !== '')
100
133
  const is_redirected = words.some(
101
- (word, index) => word === INPUT_REDIRECT && is_environment_path(words[index + 1] ?? ''),
134
+ (word, index) => word === REDIRECT_MARK && is_environment_path(words[index + 1] ?? ''),
102
135
  )
103
- const call = git_argv.program_of(segment)
104
136
 
105
137
  if (is_redirected || call === undefined) return is_redirected
106
138
 
107
- return SHELL_READERS.has(call.name) && call.args.some((word) => is_environment_path(word))
139
+ return SHELL_PROGRAMS.has(call.name) && call.args.some((word) => is_environment_path(word))
140
+ }
141
+
142
+ // A shell's quoted script holds commands of its own (`bash -c "true; cat .env"`), so it is cut again
143
+ // without regard to quotes.
144
+ function script_commands_of(segment: string): Array<string> {
145
+ const program = git_argv.program_of(segment)?.name
146
+
147
+ if (program === undefined || !SHELLS.has(program)) return []
148
+
149
+ return shell_segments.commands_of(segment)
150
+ }
151
+
152
+ // A command substitution runs even inside double quotes (`echo "$(echo x > .env)"`), which the
153
+ // quote-blanked reading takes for text, so its body is cut out as commands of their own.
154
+ function substitution_commands_of(segment: string): Array<string> {
155
+ return [...segment.matchAll(SUBSTITUTION)].flatMap((match) =>
156
+ shell_segments.commands_of(match.groups?.['dollar'] ?? match.groups?.['backtick'] ?? ''),
157
+ )
158
+ }
159
+
160
+ function segments_of(command: string): Array<string> {
161
+ return shell_segments
162
+ .quote_aware_commands_of(command)
163
+ .flatMap((segment) => [
164
+ segment,
165
+ ...script_commands_of(segment),
166
+ ...substitution_commands_of(segment),
167
+ ])
108
168
  }
109
169
 
110
- function is_environment_shell_read(command: string): boolean {
111
- return shell_segments.commands_of(command).some((segment) => is_environment_read_segment(segment))
170
+ function is_environment_shell_access(command: string): boolean {
171
+ return segments_of(command).some((segment) => is_environment_shell_segment(segment))
112
172
  }
113
173
 
114
- function is_environment_read(call: GuardedCall): boolean {
115
- if (call.name === READ_TOOL) return is_environment_path(file_path_of(call))
174
+ // A `Read` lands the secret in the transcript; an `Edit` / `Write` could set a guard switch.
175
+ function is_environment_access(call: GuardedCall): boolean {
176
+ if (call.name === READ_TOOL || is_edit_tool(call.name)) {
177
+ return is_environment_path(file_path_of(call))
178
+ }
116
179
 
117
- return bash_triggers.on_bash_command(is_environment_shell_read)(call)
180
+ return bash_triggers.on_bash_command(is_environment_shell_access)(call)
118
181
  }
119
182
 
120
183
  // The repository the settings file belongs to is two levels above it (`<root>/.claude/settings.json`).
@@ -122,7 +185,7 @@ function is_consumer_settings_edit(
122
185
  call: GuardedCall,
123
186
  is_kit_consumer: (root: string) => boolean,
124
187
  ): boolean {
125
- if (!EDIT_TOOLS.has(call.name)) return false
188
+ if (!is_edit_tool(call.name)) return false
126
189
 
127
190
  const file_path = path.resolve(file_path_of(call))
128
191
 
@@ -135,11 +198,12 @@ function is_protected_file_call(
135
198
  call: GuardedCall,
136
199
  is_kit_consumer: (root: string) => boolean = doctor_consumer.is_kit_consumer,
137
200
  ): boolean {
138
- return is_environment_read(call) || is_consumer_settings_edit(call, is_kit_consumer)
201
+ return is_environment_access(call) || is_consumer_settings_edit(call, is_kit_consumer)
139
202
  }
140
203
 
141
204
  const PROTECTED_FILE_REASON =
142
- '⛔ protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets, and a consumer ' +
205
+ '⛔ protected file: `.env`, a `.env.*` variant and `.dev.vars` hold secrets and are written only ' +
206
+ 'by the person, and a consumer ' +
143
207
  '`.claude/settings.json` is owned by `josh sync` (a change belongs upstream in kit). Leave these ' +
144
208
  'files alone, or ask the user. It fires on every occurrence.'
145
209
 
@@ -154,7 +218,7 @@ const ROW = {
154
218
  title: 'Protected file',
155
219
  topic: '`operating-rules.md`',
156
220
  fires:
157
- 'a `Read` of `.env`, or an `Edit` / `Write` of `.claude/settings.json` outside kit. Every occurrence',
221
+ 'a `Read`, `Edit` or `Write` of `.env` (or a shell read or write of it), or an `Edit` / `Write` of `.claude/settings.json` outside kit. Every occurrence',
158
222
  quiet: "kit itself, or the user's `~/.claude/settings.json`",
159
223
  },
160
224
  }
@@ -1,3 +1,5 @@
1
+ import { reply_prose } from './reply-prose'
2
+
1
3
  // Whether a reply is written in the session language. The session-language
2
4
  // line reaches a turn only through `UserPromptSubmit`, so a turn opened by a background completion or a
3
5
  // Stop hook refusal answered in whatever language it was just shown, and kept answering in it.
@@ -31,14 +33,6 @@ const MATCH_SHARE_FLOOR = 0.5
31
33
  // unless no line of the reply is longer.
32
34
  const MIN_VOTING_LETTERS = 8
33
35
 
34
- const CODE_PATTERNS: ReadonlyArray<RegExp> = [
35
- /```[\s\S]*?(?:```|$)/gu,
36
- /`[^`\n]*`/gu,
37
- /\]\([^)\s]*\)/gu,
38
- /https?:\/\/\S+/gu,
39
- ]
40
- // A run of printable ASCII; one holding a slash is a bare path the reply printed outside backticks.
41
- const ASCII_RUN_PATTERN = /[!-~]+/gu
42
36
  const LETTER_PATTERN = /\p{L}/gu
43
37
  // A letter of another script — `Common` and `Inherited` letters (`µ`) belong to no language of their own.
44
38
  const NON_LATIN_LETTER_PATTERN = /(?![\p{Script=Latn}\p{Script=Common}\p{Script=Inherited}])\p{L}/u
@@ -63,14 +57,6 @@ function letter_pattern(script: string): RegExp | undefined {
63
57
  }
64
58
  }
65
59
 
66
- function prose_of(message: string): string {
67
- let text = message
68
-
69
- for (const pattern of CODE_PATTERNS) text = text.replaceAll(pattern, ' ')
70
-
71
- return text.replaceAll(ASCII_RUN_PATTERN, (run) => (run.includes('/') ? ' ' : run))
72
- }
73
-
74
60
  function letter_count(text: string): number {
75
61
  return text.match(LETTER_PATTERN)?.length ?? 0
76
62
  }
@@ -105,7 +91,7 @@ function is_mismatch(message: string, lang: string): boolean {
105
91
  const pattern = letter_pattern(script)
106
92
  if (pattern === undefined) return false
107
93
 
108
- const prose = prose_of(message)
94
+ const prose = reply_prose.prose_of(message)
109
95
  if (letter_count(prose) < MIN_PROSE_LETTERS) return false
110
96
 
111
97
  return matches_share(prose, script, pattern) < MATCH_SHARE_FLOOR