@joshuafolkken/kit 1.1471.0 → 1.1514.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 (267) hide show
  1. package/.claude/settings.json +6 -6
  2. package/.claude/skills/epic-commands/SKILL.md +2 -14
  3. package/.claude/skills/epic-commands/epic-bundle.md +9 -5
  4. package/.claude/skills/epic-commands/execution-waves.md +1 -2
  5. package/.claude/skills/verify-ui/SKILL.md +1 -2
  6. package/.claude/skills/workflow-commands/SKILL.md +7 -9
  7. package/.claude/skills/workflow-commands/background-commands.md +3 -7
  8. package/.claude/skills/workflow-commands/backlogrun-child.md +3 -10
  9. package/.claude/skills/workflow-commands/backlogrun-lanes.md +6 -15
  10. package/.claude/skills/workflow-commands/backlogrun-park.md +2 -8
  11. package/.claude/skills/workflow-commands/backlogrun-progress.md +10 -19
  12. package/.claude/skills/workflow-commands/backlogrun-recovery.md +8 -8
  13. package/.claude/skills/workflow-commands/backlogrun-steps.md +2 -10
  14. package/.claude/skills/workflow-commands/backlogrun.md +31 -50
  15. package/.claude/skills/workflow-commands/chain-rule.md +7 -6
  16. package/.claude/skills/workflow-commands/delegation.md +12 -8
  17. package/.claude/skills/workflow-commands/entry-sequence.md +1 -3
  18. package/.claude/skills/workflow-commands/followup.md +3 -6
  19. package/.claude/skills/workflow-commands/fullrun-steps.md +3 -5
  20. package/.claude/skills/workflow-commands/fullrun.md +4 -6
  21. package/.claude/skills/workflow-commands/halfrun.md +3 -7
  22. package/.claude/skills/workflow-commands/into-target.md +1 -2
  23. package/.claude/skills/workflow-commands/issue-comments.md +3 -3
  24. package/.claude/skills/workflow-commands/issue-scout.md +3 -2
  25. package/.claude/skills/workflow-commands/latest-gate.md +2 -6
  26. package/.claude/skills/workflow-commands/needs-human-review.md +2 -5
  27. package/.claude/skills/workflow-commands/observation-filing.md +0 -14
  28. package/.claude/skills/workflow-commands/observation-ledger.md +4 -9
  29. package/.claude/skills/workflow-commands/pre-gate-cut.md +11 -16
  30. package/.claude/skills/workflow-commands/prerequisite.md +1 -2
  31. package/.claude/skills/workflow-commands/progress-watcher.md +2 -7
  32. package/.claude/skills/workflow-commands/prrun.md +3 -5
  33. package/.claude/skills/workflow-commands/retrospective.md +4 -4
  34. package/.claude/skills/workflow-commands/split-assessment.md +1 -3
  35. package/.claude/skills/workflow-commands/target-repository.md +1 -2
  36. package/.claude/skills/workflow-commands/working-tree-hold.md +2 -4
  37. package/.codex/hooks.json +6 -6
  38. package/.github/actions/setup-node/action.yml +1 -1
  39. package/.github/workflows/ci.yml +1 -1
  40. package/.github/workflows/publish.yml +2 -2
  41. package/CLAUDE.md +1 -1
  42. package/cspell/index.yaml +7 -0
  43. package/dist/CLAUDE.md +1 -1
  44. package/dist/commands/document-section-cli.js +67 -34
  45. package/dist/hooks/batch-guard.js +6 -6
  46. package/dist/hooks/chunk-4KKD2UTK.js +66 -0
  47. package/dist/hooks/{chunk-J47HU5H5.js → chunk-6TS2JD6D.js} +3 -3
  48. package/dist/hooks/chunk-7DSBJVFH.js +50 -0
  49. package/dist/hooks/{chunk-YHBZAJSV.js → chunk-A65Y4YOR.js} +3 -3
  50. package/dist/hooks/{chunk-E7OIKX5Z.js → chunk-APUO6U4J.js} +85 -7
  51. package/dist/hooks/{chunk-IX6TTTI6.js → chunk-EFXE6C37.js} +8 -22
  52. package/dist/hooks/chunk-GP4IEC4O.js +20 -0
  53. package/dist/hooks/{chunk-SHGPANFB.js → chunk-JP45SOCJ.js} +3 -3
  54. package/dist/hooks/{chunk-K4EOONYM.js → chunk-LAOV7NLM.js} +1 -1
  55. package/dist/hooks/chunk-M4HGZV7U.js +83 -0
  56. package/dist/hooks/{chunk-KC5AK2GF.js → chunk-O6GDRNJB.js} +3 -3
  57. package/dist/hooks/{chunk-YYPI7SJZ.js → chunk-PF2NZL2A.js} +5 -5
  58. package/dist/hooks/{chunk-H5HVWYXK.js → chunk-PTICX4OL.js} +3 -4
  59. package/dist/hooks/{chunk-IIGFYXKC.js → chunk-RT27QRJW.js} +581 -1438
  60. package/dist/hooks/{chunk-43ZHT22P.js → chunk-SYOTDYCI.js} +1127 -1089
  61. package/dist/hooks/{chunk-AS74OU3D.js → chunk-TNUMY6NL.js} +1507 -1336
  62. package/dist/hooks/chunk-UPUZF3AN.js +279 -0
  63. package/dist/hooks/{chunk-EUMACUK2.js → chunk-WK6SMBIC.js} +1948 -806
  64. package/dist/hooks/codex-hook-adapter.js +17 -14
  65. package/dist/hooks/duplicate-read-guard.js +7 -7
  66. package/dist/hooks/format-edited.js +5 -4
  67. package/dist/hooks/inputs.json +317 -264
  68. package/dist/hooks/investigation-guard.js +8 -11
  69. package/dist/hooks/pretool-guard.js +16 -13
  70. package/dist/hooks/session-lang.js +370 -25
  71. package/dist/hooks/stop-guard.js +2576 -176
  72. package/dist/josh.js +2124 -277
  73. package/lefthook/base.yml +5 -3
  74. package/package.json +12 -10
  75. package/prompts/collaboration-workflow/file-edits.md +1 -3
  76. package/prompts/collaboration-workflow/gh-rest.md +1 -2
  77. package/prompts/collaboration-workflow/issue-citation.md +2 -4
  78. package/prompts/collaboration-workflow/issue-template.md +10 -2
  79. package/prompts/collaboration-workflow/lane-instructions.md +35 -0
  80. package/prompts/collaboration-workflow/operating-rules.md +2 -6
  81. package/prompts/collaboration-workflow/output-bounds.md +3 -3
  82. package/prompts/collaboration-workflow/overview.md +1 -1
  83. package/prompts/collaboration-workflow/principles.md +14 -8
  84. package/prompts/collaboration-workflow/report-format.md +9 -0
  85. package/prompts/collaboration-workflow/residency.md +12 -8
  86. package/prompts/collaboration-workflow/rule-delivery.md +3 -11
  87. package/prompts/collaboration-workflow/shell-body.md +6 -6
  88. package/prompts/collaboration-workflow/turn-batching.md +5 -5
  89. package/prompts/collaboration-workflow/upstream-interrupt.md +2 -2
  90. package/prompts/collaboration-workflow/wip-cap.md +6 -6
  91. package/prompts/collaboration-workflow.md +1 -0
  92. package/prompts/review.md +4 -5
  93. package/prompts/testing-guide.md +2 -2
  94. package/scripts/agent/agent-argv.ts +20 -4
  95. package/scripts/agent/agent-role-profile.ts +101 -45
  96. package/scripts/agent/agent-safe-text.ts +60 -0
  97. package/scripts/agent/claude-agent-argv.ts +14 -0
  98. package/scripts/agent/codex-agent-argv.ts +2 -0
  99. package/scripts/agent/lane-instructions.ts +144 -0
  100. package/scripts/backlog/backlog-budget.ts +4 -4
  101. package/scripts/backlog/backlog-drive-named.ts +1 -1
  102. package/scripts/backlog/backlog-drive-restore.ts +9 -1
  103. package/scripts/backlog/backlog-occupied.ts +56 -0
  104. package/scripts/backlog/backlog-ready.ts +47 -10
  105. package/scripts/delegation/duplicate-read-guard.ts +2 -2
  106. package/scripts/delegation/investigation-guard.ts +15 -65
  107. package/scripts/delegation/investigation-reads.ts +0 -1
  108. package/scripts/document/document-byte-budget.ts +6 -5
  109. package/scripts/document/document-section-cli.ts +17 -5
  110. package/scripts/document/entry-read-budget.ts +2 -2
  111. package/scripts/document/read-set-cli.ts +4 -1
  112. package/scripts/epic/epic-audit-orphans.ts +5 -8
  113. package/scripts/epic/epic-audit.ts +52 -1
  114. package/scripts/epic/epic-bundle-evidence.ts +49 -26
  115. package/scripts/epic/epic-bundle-referenced.ts +14 -3
  116. package/scripts/epic/epic-bundle.ts +17 -9
  117. package/scripts/followup/git-followup-workflow.ts +19 -1
  118. package/scripts/gate/check-inputs.ts +129 -0
  119. package/scripts/gate/check-reuse.ts +162 -0
  120. package/scripts/gate/core-budget.ts +93 -44
  121. package/scripts/gate/core-wait.ts +130 -0
  122. package/scripts/gate/free-cores.ts +49 -0
  123. package/scripts/gate/gate-ledger.ts +17 -3
  124. package/scripts/gate/gate-plan.ts +23 -1
  125. package/scripts/gate/gate-report.ts +15 -2
  126. package/scripts/gate/gate-run-steps.ts +150 -0
  127. package/scripts/gate/held-mark.ts +55 -0
  128. package/scripts/gate/machine-capacity.ts +70 -11
  129. package/scripts/gate/pre-commit-type-check.ts +14 -1
  130. package/scripts/gate/verification-gate.ts +58 -125
  131. package/scripts/gh/git-gh-issue-rest.ts +1 -0
  132. package/scripts/gh/git-gh-issue-write.ts +24 -2
  133. package/scripts/gh/git-pr.ts +13 -1
  134. package/scripts/git/main-merge-regenerate.ts +78 -0
  135. package/scripts/git/main-merge.ts +14 -5
  136. package/scripts/hooks/batch-guard.ts +5 -8
  137. package/scripts/hooks/pre-commit-lint.ts +91 -0
  138. package/scripts/hooks/pre-push-unit.ts +1 -1
  139. package/scripts/hooks/pretool-guard.ts +27 -52
  140. package/scripts/hooks/reserved-run.ts +1 -0
  141. package/scripts/hooks/run-hook.sh +10 -12
  142. package/scripts/hooks/stop-actions.ts +204 -0
  143. package/scripts/hooks/stop-guard.ts +62 -21
  144. package/scripts/init/hook-launch.ts +12 -7
  145. package/scripts/issue/expected-effect-lint.ts +59 -0
  146. package/scripts/issue/issue-cite-cli.ts +35 -15
  147. package/scripts/issue/issue-classification.ts +1 -1
  148. package/scripts/issue/issue-lint-cli.ts +5 -3
  149. package/scripts/issue/issue-scout-cli.ts +19 -14
  150. package/scripts/issue/markdown-section.ts +19 -5
  151. package/scripts/josh/command-usage.ts +110 -0
  152. package/scripts/josh/josh-command-map.ts +2 -0
  153. package/scripts/josh/josh-command-types.ts +55 -9
  154. package/scripts/josh/josh-commands-ai.ts +13 -0
  155. package/scripts/josh/josh-commands-development.ts +1 -1
  156. package/scripts/josh/josh-commands-hooks.ts +6 -0
  157. package/scripts/josh/josh-commands-lane.ts +10 -1
  158. package/scripts/josh/josh-commands-workflow.ts +5 -0
  159. package/scripts/josh/josh-logic.ts +6 -15
  160. package/scripts/josh/josh-usage-automation.ts +121 -0
  161. package/scripts/josh/josh-usage-backlog.ts +180 -0
  162. package/scripts/josh/josh-usage-developer.ts +230 -0
  163. package/scripts/josh/josh-usage-hooks.ts +38 -0
  164. package/scripts/josh/josh-usage-issue.ts +205 -0
  165. package/scripts/josh/josh-usage-review.ts +101 -0
  166. package/scripts/josh/josh-usage-run.ts +252 -0
  167. package/scripts/josh/josh-usage-session.ts +238 -0
  168. package/scripts/josh/josh-usage-workflow.ts +212 -0
  169. package/scripts/josh/josh.ts +14 -5
  170. package/scripts/josh/package-directory.ts +20 -0
  171. package/scripts/lane/lane-capacity.ts +8 -2
  172. package/scripts/lane/lane-check-stats.ts +56 -0
  173. package/scripts/lane/lane-child-invocation.ts +35 -4
  174. package/scripts/lane/lane-dispatch.ts +3 -3
  175. package/scripts/lane/lane-guard-policy.ts +2 -2
  176. package/scripts/lane/lane-handoff.ts +8 -1
  177. package/scripts/lane/lane-install.ts +12 -4
  178. package/scripts/lane/lane-ledger.ts +43 -1
  179. package/scripts/lane/lane-limit-cli.ts +8 -9
  180. package/scripts/lane/lane-measure-cli.ts +2 -0
  181. package/scripts/lane/lane-preserve.ts +29 -1
  182. package/scripts/lane/lane-registry.ts +9 -7
  183. package/scripts/lane/lane-restart.ts +5 -26
  184. package/scripts/lane/lane-session-action.ts +146 -0
  185. package/scripts/lane/lane-session-broker.ts +57 -138
  186. package/scripts/lane/lane-withdraw-cli.ts +71 -0
  187. package/scripts/lane/lane-withdraw.ts +182 -0
  188. package/scripts/lint/format.ts +4 -4
  189. package/scripts/lint/lint-parallel.ts +10 -3
  190. package/scripts/lint/lint-related-scope.ts +4 -1
  191. package/scripts/notify/telegram-test.ts +10 -5
  192. package/scripts/report/report-format-reference.ts +2 -0
  193. package/scripts/review/acceptance-map-check.ts +61 -0
  194. package/scripts/review/acceptance-map.ts +205 -0
  195. package/scripts/review/review-brief-cli.ts +1 -1
  196. package/scripts/rules/agent-model.ts +59 -0
  197. package/scripts/rules/delivered-rules.ts +11 -2
  198. package/scripts/rules/filing-cap.ts +1 -0
  199. package/scripts/rules/firing-point.ts +6 -5
  200. package/scripts/rules/guard-remedy.ts +117 -0
  201. package/scripts/rules/issue-comments.ts +67 -6
  202. package/scripts/rules/last-prompt.ts +50 -5
  203. package/scripts/rules/oracle-list-cli.ts +7 -0
  204. package/scripts/rules/piped-verification.ts +3 -1
  205. package/scripts/rules/rule-list.ts +4 -1
  206. package/scripts/rules/rule-registry-rows.ts +10 -0
  207. package/scripts/rules/rule-registry.ts +7 -4
  208. package/scripts/rules/rule-rewrite.ts +114 -0
  209. package/scripts/rules/rule-value.ts +7 -1
  210. package/scripts/rules/shell-segments.ts +15 -0
  211. package/scripts/rules/stop-decision.ts +77 -0
  212. package/scripts/rules/stop-rules.ts +147 -47
  213. package/scripts/rules/tail-commands.ts +15 -1
  214. package/scripts/run/board/run-board-cli.ts +2 -0
  215. package/scripts/run/board/run-board-closed.ts +8 -2
  216. package/scripts/run/board/run-board-github.ts +9 -1
  217. package/scripts/run/board/run-board-header.ts +4 -1
  218. package/scripts/run/board/run-board-labels.ts +2 -0
  219. package/scripts/run/board/run-board-layout.ts +1 -1
  220. package/scripts/run/board/run-board-read.ts +16 -0
  221. package/scripts/run/board/run-board-reason.ts +10 -1
  222. package/scripts/run/board/run-board-render-legend.ts +1 -0
  223. package/scripts/run/board/run-board-render-slot.ts +1 -1
  224. package/scripts/run/board/run-board-render-waits.ts +41 -0
  225. package/scripts/run/board/run-board-render.ts +6 -1
  226. package/scripts/run/board/run-board-row-yield.ts +1 -0
  227. package/scripts/run/board/run-board-state.ts +5 -0
  228. package/scripts/run/board/run-board-status.ts +72 -21
  229. package/scripts/run/board/run-board-tick.ts +5 -1
  230. package/scripts/run/cut/run-cut-cli.ts +3 -2
  231. package/scripts/run/cut/run-cut-report.ts +16 -0
  232. package/scripts/run/detached-launch.ts +23 -4
  233. package/scripts/run/entry/run-entry-cli.ts +8 -1
  234. package/scripts/run/event/run-event-render.ts +1 -0
  235. package/scripts/run/event/run-event-scope.ts +19 -1
  236. package/scripts/run/event/run-event-stream-emit.ts +13 -6
  237. package/scripts/run/event/run-event-stream.ts +4 -0
  238. package/scripts/run/hold/run-hold-session.ts +46 -0
  239. package/scripts/run/hold/run-hold.ts +7 -1
  240. package/scripts/run/launch-priority.ts +32 -0
  241. package/scripts/run/merge/run-merge-cli.ts +16 -65
  242. package/scripts/run/merge/run-merge-signals.ts +99 -0
  243. package/scripts/run/merge/run-merge-steps.ts +11 -1
  244. package/scripts/run/merge/run-merge.ts +21 -9
  245. package/scripts/run/run-liveness.ts +1 -1
  246. package/scripts/run/run-poll-hook.ts +157 -0
  247. package/scripts/run/run-step-cli.ts +22 -15
  248. package/scripts/run/run-step-section.ts +85 -0
  249. package/scripts/run/ship/run-ship-cli.ts +5 -12
  250. package/scripts/run/ship/run-ship-detach.ts +9 -0
  251. package/scripts/run/ship/run-ship-next.ts +10 -2
  252. package/scripts/run/ship/run-ship-overlap.ts +195 -0
  253. package/scripts/run/ship/run-ship-pre-detach.ts +33 -15
  254. package/scripts/run/ship/run-ship-preflight.ts +28 -2
  255. package/scripts/run/ship/run-ship-return.ts +31 -7
  256. package/scripts/run/ship/run-ship-review-steps.ts +12 -10
  257. package/scripts/run/ship/run-ship-steps.ts +1 -1
  258. package/scripts/run/ship/run-ship-stop-context.ts +206 -0
  259. package/scripts/run/ship/run-ship-sync.ts +14 -6
  260. package/scripts/test/test-e2e-guard.ts +30 -5
  261. package/scripts/test/test-unit-guard.ts +16 -1
  262. package/scripts/test/unit-worker-share.ts +26 -4
  263. package/scripts/time-runtime/time-shell.ts +29 -4
  264. package/scripts/time-runtime/time-spans.ts +1 -0
  265. package/scripts/time-runtime/time-tool-call.ts +7 -0
  266. package/templates/gitignore +6 -2
  267. package/dist/hooks/chunk-K4LMBQC2.js +0 -68
@@ -101,16 +101,26 @@ async function local_slug_for(targets: ReadonlyArray<CiteTarget>): Promise<strin
101
101
  return has_bare_target ? await git_gh_command.repo_get_name_with_owner() : undefined
102
102
  }
103
103
 
104
+ // The citation lines and the failure lines of one invocation, apart.
105
+ interface CiteReport {
106
+ lines: ReadonlyArray<string>
107
+ failures: ReadonlyArray<string>
108
+ }
109
+
110
+ function split_results(results: ReadonlyArray<LineResult>): CiteReport {
111
+ return {
112
+ lines: results.filter((result) => result.kind === 'ok').map((result) => result.line),
113
+ failures: results.filter((result) => result.kind === 'fail').map((result) => result.line),
114
+ }
115
+ }
116
+
104
117
  // Citations to stdout so the block stays paste-ready; failures to stderr so each unanswered number is
105
118
  // named without landing in what gets pasted. A failure sets a non-zero exit.
106
- function report(results: ReadonlyArray<LineResult>): number {
107
- const lines = results.filter((result) => result.kind === 'ok').map((result) => result.line)
108
- const failures = results.filter((result) => result.kind === 'fail').map((result) => result.line)
109
-
110
- if (lines.length > 0) console.info(lines.join(LINE_SEPARATOR))
111
- for (const failure of failures) console.error(failure)
119
+ function report(cited: CiteReport): number {
120
+ if (cited.lines.length > 0) console.info(cited.lines.join(LINE_SEPARATOR))
121
+ for (const failure of cited.failures) console.error(failure)
112
122
 
113
- return failures.length === 0 ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
123
+ return cited.failures.length === 0 ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
114
124
  }
115
125
 
116
126
  // The targets the command line names, or `undefined` for a line nobody can read — an unknown flag and
@@ -122,14 +132,11 @@ function read_targets(args: ReadonlyArray<string>): ReadonlyArray<CiteTarget> |
122
132
  return parse_targets(parsed.positionals, parsed.values.repo)
123
133
  }
124
134
 
125
- async function run(args: ReadonlyArray<string>): Promise<number> {
135
+ // The lines `issue:cite` prints for `args`, without printing them — the `Stop` hook shows them itself
136
+ // (`stop-actions.ts`). `undefined` for a line nobody can read.
137
+ async function cite(args: ReadonlyArray<string>): Promise<CiteReport | undefined> {
126
138
  const targets = read_targets(args)
127
-
128
- if (targets === undefined) {
129
- console.error(USAGE)
130
-
131
- return FAILURE_EXIT_CODE
132
- }
139
+ if (targets === undefined) return undefined
133
140
 
134
141
  const local_slug = await local_slug_for(targets)
135
142
  const results = await bounded_pool.bounded_map(
@@ -138,7 +145,19 @@ async function run(args: ReadonlyArray<string>): Promise<number> {
138
145
  async (target) => await cite_target(target, local_slug),
139
146
  )
140
147
 
141
- return report(results)
148
+ return split_results(results)
149
+ }
150
+
151
+ async function run(args: ReadonlyArray<string>): Promise<number> {
152
+ const cited = await cite(args)
153
+
154
+ if (cited === undefined) {
155
+ console.error(USAGE)
156
+
157
+ return FAILURE_EXIT_CODE
158
+ }
159
+
160
+ return report(cited)
142
161
  }
143
162
 
144
163
  async function main(args: ReadonlyArray<string>): Promise<void> {
@@ -147,6 +166,7 @@ async function main(args: ReadonlyArray<string>): Promise<void> {
147
166
 
148
167
  const issue_cite_cli = {
149
168
  USAGE,
169
+ cite,
150
170
  parse_targets,
151
171
  run,
152
172
  }
@@ -2,7 +2,7 @@ import { issue_bug_label } from './issue-bug-label'
2
2
  import { BREAKING_CHANGE_LABEL, BUG_LABEL, ENHANCEMENT_LABEL, has_label_name } from './issue-labels'
3
3
  import { markdown_section } from './markdown-section'
4
4
 
5
- const FEATURE_DECLARATIONS = ['- 目的: 機能追加', '- 目的: 機能改善']
5
+ const FEATURE_DECLARATIONS = ['- 目的: 機能追加', '- 目的: 機能改善', '- 目的: 時間短縮']
6
6
  const BREAKING_DECLARATION = '- 互換性: 破壊的変更'
7
7
  const NON_BUG_DECLARATION = '- 種別: 非不具合'
8
8
  const CLASSIFICATION_LABELS = [BUG_LABEL, ENHANCEMENT_LABEL, BREAKING_CHANGE_LABEL]
@@ -2,6 +2,7 @@
2
2
  import { readFile } from 'node:fs/promises'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { behavior_change_lint } from './behavior-change-lint'
5
+ import { expected_effect_lint } from './expected-effect-lint'
5
6
  import { issue_classification } from './issue-classification'
6
7
  import { issue_lint } from './issue-lint'
7
8
 
@@ -9,7 +10,7 @@ import { issue_lint } from './issue-lint'
9
10
  // one per line, exiting non-zero. It reads a path rather than stdin so it can
10
11
  // lint a body written to a file before the `gh api … issues` call that files it. A body that declares
11
12
  // itself a behavior-change Issue is additionally held to the firing-point, baseline and reproduction
12
- // rules.
13
+ // rules, and one that declares a run-time-shortening aim to the expected-effect estimate.
13
14
 
14
15
  const SUCCESS_EXIT_CODE = 0
15
16
  const FAILURE_EXIT_CODE = 1
@@ -17,14 +18,15 @@ const OK_MESSAGE = 'ok'
17
18
  const NO_LABELS = 'none'
18
19
  const USAGE = 'Usage: josh issue:lint <path-to-issue-body>'
19
20
 
20
- // Every problem a body has: the four base template headings it lacks, then the behavior-change
21
- // problems (empty for an Issue that does not declare itself one).
21
+ // Every problem a body has: the four base template headings it lacks, then the behavior-change and
22
+ // expected-effect problems (each empty for an Issue that does not declare itself one).
22
23
  function problems(body: string): ReadonlyArray<string> {
23
24
  const missing = issue_lint.missing_headings(body).map((heading) => `missing heading: ${heading}`)
24
25
 
25
26
  return [
26
27
  ...missing,
27
28
  ...behavior_change_lint.problems(body),
29
+ ...expected_effect_lint.problems(body),
28
30
  ...issue_classification.problems(body),
29
31
  ]
30
32
  }
@@ -103,7 +103,7 @@ function draft_of(args: ScoutArguments, repo: string): BacklogIssue {
103
103
  }
104
104
 
105
105
  // The epic beside a candidate is the placement answer for a draft that cites nothing: the epic half
106
- // below decides from prose references, and a title-only draft has none to give it.
106
+ // below decides from signal references, and a title-only draft has none to give it.
107
107
  function format_duplicate(candidate: DuplicateCandidate): string {
108
108
  const score = candidate.is_referenced === true ? 'ref' : candidate.score.toFixed(SCORE_DIGITS)
109
109
  const epic = candidate.epic === undefined ? '' : ` (epic ${session_cite.issue(candidate.epic)})`
@@ -162,12 +162,13 @@ function format_epic(decision: BundleDecision): Array<string> {
162
162
  return lines
163
163
  }
164
164
 
165
- // A draft cites nothing, so the epic half has nothing to decide from: its two signals are prose
166
- // references and recorded dependencies, and a title carries neither. Saying "file it standalone" there
167
- // reports a scan that found nothing when none was possible — the confident wrong answer the gap
165
+ // A draft cites nothing where a citation counts, so the epic half has nothing to decide from: its two
166
+ // signals are references in the acceptance criteria or the `parent: #N` line and recorded
167
+ // dependencies, and a title or a background mention carries neither. Saying "file it standalone"
168
+ // there reports a scan that found nothing when none was possible — the confident wrong answer the gap
168
169
  // warnings exist to prevent everywhere else in this command.
169
170
  const NO_REFERENCE_LINE =
170
- 'Epic: not asked — no issue number in the summary. Pass --body "…#<N>…", or take the epic printed beside a duplicate above.'
171
+ 'Epic: not asked — no issue number in the summary\'s acceptance criteria or parent line. Pass --body "parent: #<N>", or take the epic printed beside a duplicate above.'
171
172
 
172
173
  // Every placing verdict above asserts that no epic already tracks the draft's relatives, and a cut
173
174
  // epic listing cannot support that: `epic:bundle` withholds them for it,
@@ -195,18 +196,22 @@ function format_epic_decision(
195
196
  return format_epic(decision).join('\n')
196
197
  }
197
198
 
198
- // Whether the summary names an issue at all, read through the same parser the decision itself uses —
199
- // a second reading of "does this body cite anything" could disagree with the one that matters.
200
- function has_reference(draft: BacklogIssue): boolean {
201
- return epic_audit_logic.parse_references(draft.body, draft.repo).length > 0
199
+ // The issue numbers the summary names where `decide_bundle` counts a reference — its acceptance
200
+ // criteria and its `parent: #N` line — read through the same parser the decision itself uses: a
201
+ // second reading of "does this body cite anything" could disagree with the one that matters, and an
202
+ // issue cited only as the background of the filing is not one the draft belongs with.
203
+ function signal_numbers(draft: BacklogIssue): Array<number> {
204
+ return epic_audit_logic
205
+ .signal_references(draft.body, draft.repo)
206
+ .map((reference) => reference.number)
202
207
  }
203
208
 
204
209
  // The open epics the summary names outright. `decide_bundle` excludes an epic from the candidate pool
205
- // — a container is not a sibling — so a draft saying "part of epic #1153", which is the phrasing the
206
- // workflow documents themselves suggest, otherwise reaches `none` and is told to file standalone with
207
- // the epic it named never mentioned.
210
+ // — a container is not a sibling — so a draft whose `parent: #1153` line names the epic, which is the
211
+ // phrasing the workflow documents themselves suggest, otherwise reaches `none` and is told to file
212
+ // standalone with the epic it named never mentioned.
208
213
  function named_epics(draft: BacklogIssue, issues: ReadonlyArray<BacklogIssue>): Array<number> {
209
- const named = new Set(epic_audit_logic.parse_references(draft.body, draft.repo))
214
+ const named = new Set(signal_numbers(draft))
210
215
 
211
216
  return issues
212
217
  .filter((issue) => issue.is_epic === true && named.has(issue.number))
@@ -241,7 +246,7 @@ function format_epic_answer(
241
246
 
242
247
  if (named.length > 0) return format_named_epics(named, decision).join('\n')
243
248
 
244
- return format_epic_decision(decision, has_reference(draft), is_membership_established)
249
+ return format_epic_decision(decision, signal_numbers(draft).length > 0, is_membership_established)
245
250
  }
246
251
 
247
252
  // The closed rows reach the duplicate half only. `decide_bundle` places a draft among issues that
@@ -3,6 +3,9 @@
3
3
  // way, so the slicing lives in one place rather than being written twice.
4
4
 
5
5
  const HEADING_PREFIX = '## '
6
+ // A body saved from GitHub's web editor ends its lines with CRLF; a kept `\r` would end every line's
7
+ // text with it and defeat each `$`-anchored match downstream.
8
+ const LINE_BREAK_PATTERN = /\r?\n/u
6
9
 
7
10
  function is_heading(line: string): boolean {
8
11
  return line.trimStart().startsWith(HEADING_PREFIX)
@@ -29,21 +32,31 @@ function next_fence(line: string, fence: string): string | undefined {
29
32
  return undefined
30
33
  }
31
34
 
32
- function unfenced_lines(body: string): ReadonlyArray<string> {
35
+ function lines_of(body: string): ReadonlyArray<string> {
36
+ return body.split(LINE_BREAK_PATTERN)
37
+ }
38
+
39
+ // The body's lines with every fenced line, fences included, blanked. Indentation is kept, so a list
40
+ // item's indented continuation survives.
41
+ function fence_free_lines(body: string): ReadonlyArray<string> {
33
42
  let fence = ''
34
43
  const visible: Array<string> = []
35
44
 
36
- for (const line of body.split('\n')) {
45
+ for (const line of lines_of(body)) {
37
46
  const next = next_fence(line, fence)
38
- const visible_line = line.replace(/^(?: {4}|\t).*/u, '')
39
47
 
40
- visible.push(next === undefined && fence === '' ? visible_line : '')
48
+ visible.push(next === undefined && fence === '' ? line : '')
41
49
  fence = next ?? fence
42
50
  }
43
51
 
44
52
  return visible
45
53
  }
46
54
 
55
+ // As `fence_free_lines`, with an indented code line (4 spaces or a tab) blanked too.
56
+ function unfenced_lines(body: string): ReadonlyArray<string> {
57
+ return fence_free_lines(body).map((line) => line.replace(/^(?: {4}|\t).*/u, ''))
58
+ }
59
+
47
60
  function has_unfenced_line(body: string, text: string): boolean {
48
61
  return unfenced_lines(body).some((line) => line.trim() === text)
49
62
  }
@@ -59,7 +72,7 @@ function section_lines_matching(
59
72
  body: string,
60
73
  is_target: (line: string) => boolean,
61
74
  ): ReadonlyArray<string> {
62
- const lines = body.split('\n')
75
+ const lines = lines_of(body)
63
76
  const visible = unfenced_lines(body)
64
77
  const start = visible.findIndex((line) => is_target(line))
65
78
 
@@ -80,6 +93,7 @@ function section_lines(body: string, heading: string): ReadonlyArray<string> {
80
93
  const markdown_section = {
81
94
  section_lines,
82
95
  section_lines_matching,
96
+ fence_free_lines,
83
97
  unfenced_lines,
84
98
  has_line,
85
99
  has_unfenced_line,
@@ -0,0 +1,110 @@
1
+ import { canonical_command, COMMAND_MAP } from './josh-command-map'
2
+ import type { CommandUsage } from './josh-command-types'
3
+ import { AUTOMATION_USAGE } from './josh-usage-automation'
4
+ import { BACKLOG_USAGE } from './josh-usage-backlog'
5
+ import { DEVELOPER_USAGE } from './josh-usage-developer'
6
+ import { HOOK_USAGE } from './josh-usage-hooks'
7
+ import { ISSUE_USAGE } from './josh-usage-issue'
8
+ import { REVIEW_USAGE } from './josh-usage-review'
9
+ import { RUN_USAGE } from './josh-usage-run'
10
+ import { SESSION_USAGE } from './josh-usage-session'
11
+ import { WORKFLOW_USAGE } from './josh-usage-workflow'
12
+
13
+ // The single renderer of a command's usage. `josh help <command>` prints it and
14
+ // `scripts/document/generate-command-usage.ts` writes it between a reference page's usage markers,
15
+ // so the terminal and the page are one text (#3932).
16
+
17
+ type UsageTable = Readonly<Record<string, CommandUsage>>
18
+
19
+ // Which reference page renders each usage table — the one place a table is tied to its page, so the
20
+ // generator's coverage check and `josh help` read the same set (#3981).
21
+ /* eslint-disable @typescript-eslint/naming-convention */
22
+ const USAGE_PAGE_TABLES: Readonly<Record<string, ReadonlyArray<UsageTable>>> = {
23
+ 'docs/josh-commands.md': [DEVELOPER_USAGE],
24
+ 'docs/josh-commands-run.md': [RUN_USAGE, SESSION_USAGE],
25
+ 'docs/josh-commands-backlog.md': [ISSUE_USAGE, BACKLOG_USAGE, REVIEW_USAGE],
26
+ 'docs/josh-commands-automation.md': [AUTOMATION_USAGE, WORKFLOW_USAGE, HOOK_USAGE],
27
+ }
28
+ /* eslint-enable @typescript-eslint/naming-convention */
29
+ const COMMAND_USAGE: UsageTable = Object.fromEntries(
30
+ Object.values(USAGE_PAGE_TABLES)
31
+ .flat()
32
+ .flatMap((table) => Object.entries(table)),
33
+ )
34
+ const NOTE_GAP = 3
35
+
36
+ interface ExampleLine {
37
+ command: string
38
+ note: string | undefined
39
+ }
40
+
41
+ function usage_for(name: string): CommandUsage | undefined {
42
+ return Object.hasOwn(COMMAND_USAGE, name) ? COMMAND_USAGE[name] : undefined
43
+ }
44
+
45
+ function example_line(name: string, example: CommandUsage['examples'][number]): ExampleLine {
46
+ const command = example.arguments ? `pnpm josh ${name} ${example.arguments}` : `pnpm josh ${name}`
47
+
48
+ return { command, note: example.note }
49
+ }
50
+
51
+ // Every comment starts in one column, so a block reads as a table of what each spelling does.
52
+ function format_examples(name: string, usage: CommandUsage): string {
53
+ const lines = usage.examples.map((example) => example_line(name, example))
54
+ const width = Math.max(...lines.map((line) => line.command.length)) + NOTE_GAP
55
+ const rendered = lines.map((line) =>
56
+ line.note === undefined ? line.command : `${line.command.padEnd(width)}# ${line.note}`,
57
+ )
58
+
59
+ return ['```bash', ...rendered, '```'].join('\n')
60
+ }
61
+
62
+ function code_span(text: string): string {
63
+ return `\`${text}\``
64
+ }
65
+
66
+ function format_flags(flags: NonNullable<CommandUsage['flags']>): string {
67
+ const items = flags.map(
68
+ (flag) =>
69
+ `- ${flag.names.map((spelling) => code_span(spelling)).join(' / ')} — ${flag.description}`,
70
+ )
71
+
72
+ return ['**Options:**', '', ...items].join('\n')
73
+ }
74
+
75
+ function format_usage(name: string): string | undefined {
76
+ const usage = usage_for(name)
77
+ if (usage === undefined) return undefined
78
+ const flags = usage.flags === undefined ? [] : [format_flags(usage.flags)]
79
+
80
+ return [format_examples(name, usage), ...flags].join('\n\n')
81
+ }
82
+
83
+ // `josh help <command>`: the synopsis line, the description, then the usage block when the command
84
+ // has one. An alias resolves to its command; a name that is neither answers `undefined`.
85
+ function format_command_help(input: string): string | undefined {
86
+ const name = canonical_command(input)
87
+ const entry = Object.hasOwn(COMMAND_MAP, name) ? COMMAND_MAP[name] : undefined
88
+ if (entry === undefined) return undefined
89
+ const [synopsis] = entry.reference
90
+ const head = synopsis ? `josh ${name} ${synopsis}` : `josh ${name}`
91
+ const usage = format_usage(name)
92
+
93
+ return [head, '', entry.description, ...(usage === undefined ? [] : ['', usage])].join('\n')
94
+ }
95
+
96
+ // The commands whose usage blocks a reference page renders — the keys of the tables tied to it.
97
+ function page_commands(page: string): Array<string> {
98
+ const tables = Object.hasOwn(USAGE_PAGE_TABLES, page) ? USAGE_PAGE_TABLES[page] : undefined
99
+
100
+ return (tables ?? []).flatMap((table) => Object.keys(table))
101
+ }
102
+
103
+ const command_usage = {
104
+ USAGE_PAGES: Object.keys(USAGE_PAGE_TABLES),
105
+ format_command_help,
106
+ format_usage,
107
+ page_commands,
108
+ }
109
+
110
+ export { command_usage }
@@ -77,6 +77,7 @@ const ALIASES: Record<string, string> = {
77
77
  tm: 'time',
78
78
  tmd: 'time:density',
79
79
  rtr: 'retrospective',
80
+ ca: 'cost:audit',
80
81
  rvf: 'review:findings',
81
82
  ruv: 'rule:value',
82
83
  ev: 'eval',
@@ -86,6 +87,7 @@ const ALIASES: Record<string, string> = {
86
87
  lst: 'lane:stats',
87
88
  lli: 'lane:limit',
88
89
  lrs: 'lane:restart',
90
+ lwd: 'lane:withdraw',
89
91
  blr: 'backlogrun',
90
92
  }
91
93
 
@@ -43,6 +43,25 @@ type CommandReference = readonly [
43
43
  side_effects: ReadonlyArray<CommandSideEffect>,
44
44
  ]
45
45
 
46
+ // One `pnpm josh <command> <arguments>` line of a usage block, with the comment printed after it.
47
+ interface CommandExample {
48
+ arguments: string
49
+ note?: string
50
+ }
51
+
52
+ // Spellings of one option — `-y` and `--yes` — rendered side by side.
53
+ interface CommandFlag {
54
+ names: ReadonlyArray<string>
55
+ description: string
56
+ }
57
+
58
+ // What `josh help <command>` prints and what the reference pages render between their usage
59
+ // markers — one source for both, so the page and the terminal cannot disagree (#3932).
60
+ interface CommandUsage {
61
+ examples: ReadonlyArray<CommandExample>
62
+ flags?: ReadonlyArray<CommandFlag>
63
+ }
64
+
46
65
  interface CommandEntry {
47
66
  script?: string
48
67
  shell?: ReadonlyArray<string>
@@ -109,6 +128,13 @@ const TS_BUILD_INFO_FILE = '.tsbuildinfo'
109
128
  const CSPELL_CACHE_FILE = '.cspellcache'
110
129
  const ESLINT_EDIT_CACHE_FILE = '.eslintcache.edit'
111
130
  const ESLINT_RELATED_CACHE_FILE = '.eslintcache.related'
131
+ const PRETTIER_CACHE_FILE = '.prettiercache'
132
+ const PRETTIER_RELATED_CACHE_FILE = '.prettiercache.related'
133
+ // The pre-commit hook's own, because a staged-files run on the gate's file degrades it: cspell prunes
134
+ // every entry the run did not visit (measured 892 KB down to 18 KB), and `eslint --quiet` stores a
135
+ // report without its warnings under the same config hash the gate reads.
136
+ const CSPELL_COMMIT_CACHE_FILE = '.cspellcache.commit'
137
+ const ESLINT_COMMIT_CACHE_FILE = '.eslintcache.commit'
112
138
 
113
139
  type GateCachePortability = 'raw' | 'rooted-json'
114
140
 
@@ -139,6 +165,10 @@ const ESLINT_RELATED_CACHE_SPEC: GateCacheSpec = {
139
165
  cache_file: ESLINT_RELATED_CACHE_FILE,
140
166
  portability: ROOTED_JSON_PORTABILITY,
141
167
  }
168
+ // **Prettier's caches are in neither list: they do not survive a move to another checkout.**
169
+ // Prettier 3 hashes each file's resolved options, *its absolute `filepath` included*, into the entry,
170
+ // so rebasing the keys as ESLint's are still leaves every entry a miss — two checkouts of one tree
171
+ // were measured sharing none of their 3,526 option hashes. Each checkout keeps a warm cache of its own.
142
172
  const GATE_CACHE_SPECS: ReadonlyArray<GateCacheSpec> = [
143
173
  ESLINT_CACHE_SPEC,
144
174
  TS_CACHE_SPEC,
@@ -175,14 +205,20 @@ const GATE_CACHE_FILES: ReadonlyArray<string> = GATE_CACHE_SPECS.map((spec) => s
175
205
  //
176
206
  // **The lint's target scope is untouched.** This is where a cache is written, not what is read:
177
207
  // `josh lint:related` narrows in front of the gate and `josh gate` still runs `josh lint` over the
178
- // whole tree before any commit.
208
+ // whole tree before any commit. The scoped lint's prettier cache is its own for the same reason:
209
+ // prettier rewrites its cache file whole as well.
179
210
  // What the ignore rules are asserted against: every cache file this package writes, wherever it is
180
- // written from. The gate's three, the edit hook's, and the scoped lint's — a cache file that is not
181
- // ignored is committed, or spell-checked, which is a red gate with nothing misspelled in the tree.
211
+ // written from. The gate's, the edit hook's, the scoped lint's and the pre-commit hook's — a cache
212
+ // file that is not ignored is committed, or spell-checked, which is a red gate with nothing
213
+ // misspelled in the tree.
182
214
  const IGNORED_CACHE_FILES: ReadonlyArray<string> = [
183
215
  ...GATE_CACHE_FILES,
184
216
  ESLINT_EDIT_CACHE_FILE,
185
217
  ESLINT_RELATED_CACHE_FILE,
218
+ PRETTIER_CACHE_FILE,
219
+ PRETTIER_RELATED_CACHE_FILE,
220
+ CSPELL_COMMIT_CACHE_FILE,
221
+ ESLINT_COMMIT_CACHE_FILE,
186
222
  ]
187
223
 
188
224
  // `--cache --cache-strategy content` is one convention rather than two coincidences: cspell adopted
@@ -190,11 +226,11 @@ const IGNORED_CACHE_FILES: ReadonlyArray<string> = [
190
226
  const CONTENT_CACHE_FLAGS = ['--cache', '--cache-strategy', 'content'] as const
191
227
  const CACHE_LOCATION_FLAG = '--cache-location'
192
228
 
193
- // Four locations are written with these same four flags — the gate's eslint cache, the scoped lint's,
194
- // the edit hook's and the spell check's — so the sequence is built here once. Spelled out per
195
- // location, a fix to the order would have to be made four times and each copy fails silently: a flag
196
- // and its value are one unit, and a wrong order makes the tool read `--cache-strategy` as the file
197
- // name rather than erroring.
229
+ // Every location is written with these same four flags — the eslint, prettier and spell-check caches
230
+ // of the gate, the scoped lint, the edit hook and the pre-commit hook — so the sequence is built here
231
+ // once. Spelled out per location, a fix to the order would have to be made once per location and
232
+ // each copy fails silently: a flag and its value are one unit, and a wrong order makes the tool read
233
+ // `--cache-strategy` as the file name rather than erroring.
198
234
  function content_cache_flags(cache_file: string): ReadonlyArray<string> {
199
235
  return [...CONTENT_CACHE_FLAGS, CACHE_LOCATION_FLAG, cache_file]
200
236
  }
@@ -204,8 +240,12 @@ const ESLINT_EDIT_CACHE_FLAGS = content_cache_flags(ESLINT_EDIT_CACHE_FILE)
204
240
  const ESLINT_RELATED_CACHE_FLAGS = content_cache_flags(ESLINT_RELATED_CACHE_FILE)
205
241
  const TS_CACHE_FLAGS = ['--incremental', '--tsBuildInfoFile', TS_BUILD_INFO_FILE] as const
206
242
  const CSPELL_CACHE_FLAGS = content_cache_flags(CSPELL_CACHE_FILE)
243
+ const PRETTIER_CACHE_FLAGS = content_cache_flags(PRETTIER_CACHE_FILE)
244
+ const PRETTIER_RELATED_CACHE_FLAGS = content_cache_flags(PRETTIER_RELATED_CACHE_FILE)
245
+ const CSPELL_COMMIT_CACHE_FLAGS = content_cache_flags(CSPELL_COMMIT_CACHE_FILE)
246
+ const ESLINT_COMMIT_CACHE_FLAGS = content_cache_flags(ESLINT_COMMIT_CACHE_FILE)
207
247
 
208
- export type { CommandCategory, CommandEntry, GateCacheSpec }
248
+ export type { CommandCategory, CommandEntry, CommandUsage, GateCacheSpec }
209
249
  // The three cache files are exported one by one as well as as a list, because `josh bench` clears
210
250
  // them per target: the lint step writes only the eslint one, so a target
211
251
  // that cleared the list would report a cold type check as the lint's own cost. The edit hook's cache
@@ -216,8 +256,10 @@ export {
216
256
  COMMAND_SIDE_EFFECTS,
217
257
  CSPELL_CACHE_FILE,
218
258
  CSPELL_CACHE_FLAGS,
259
+ CSPELL_COMMIT_CACHE_FLAGS,
219
260
  ESLINT_CACHE_FILE,
220
261
  ESLINT_CACHE_FLAGS,
262
+ ESLINT_COMMIT_CACHE_FLAGS,
221
263
  ESLINT_EDIT_CACHE_FLAGS,
222
264
  ESLINT_RELATED_CACHE_FLAGS,
223
265
  ESLINT_RELATED_CACHE_FILE,
@@ -226,6 +268,10 @@ export {
226
268
  GATE_COMMAND,
227
269
  IGNORED_CACHE_FILES,
228
270
  OPTIONAL_ENV_FILE_FLAGS,
271
+ PRETTIER_CACHE_FILE,
272
+ PRETTIER_CACHE_FLAGS,
273
+ PRETTIER_RELATED_CACHE_FILE,
274
+ PRETTIER_RELATED_CACHE_FLAGS,
229
275
  SHARED_CACHE_SPECS,
230
276
  TS_BUILD_INFO_FILE,
231
277
  TS_CACHE_FLAGS,
@@ -1,3 +1,4 @@
1
+ import { core_budget } from '#scripts/gate/core-budget'
1
2
  import { OPTIONAL_ENV_FILE_FLAGS, type CommandEntry } from './josh-command-types'
2
3
  import { BACKLOG_COMMANDS } from './josh-commands-backlog'
3
4
  import { DOCUMENT_COMMANDS } from './josh-commands-document'
@@ -80,6 +81,16 @@ const AI_COMMANDS: Record<string, CommandEntry> = {
80
81
  category: 'AI tools',
81
82
  reference: ['(--cut | --over <tokens-per-request>) [--path <dir>]', 'automation', ['none']],
82
83
  },
84
+ 'cost:audit': {
85
+ script: 'scripts/cost-audit/cost-audit-cli.ts',
86
+ description:
87
+ 'Report AI cost over a period by session kind, tool category and guard, with cost per merge and regressions against the period before',
88
+ category: 'AI tools',
89
+ reference: ['[--period <days>]', 'maintainer', ['none']],
90
+ // Kit-only: it reads kit's own transcript store, like `josh retrospective`, which carries its
91
+ // summary line.
92
+ is_kit_only: true,
93
+ },
83
94
  ...DOCUMENT_COMMANDS,
84
95
  time: {
85
96
  script: 'scripts/time/time-cli.ts',
@@ -108,6 +119,8 @@ const AI_COMMANDS: Record<string, CommandEntry> = {
108
119
  "Aggregate a finished run's cost, review findings, observation ledger and events into one digest",
109
120
  category: 'AI tools',
110
121
  reference: ['', 'maintainer', ['none']],
122
+ core_weight: core_budget.CORE_WEIGHTS.retrospective,
123
+ memory_mb: core_budget.MEMORY_MB.retrospective,
111
124
  // Kit-only: it reads kit's own development run — the transcript store, the review ledger and the
112
125
  // run event stream — so it means nothing in a consumer project and is dropped from a consumer's
113
126
  // help, through the same declaration `josh time` uses for the same reason.
@@ -129,7 +129,7 @@ const DEV_COMMANDS: Record<string, CommandEntry> = {
129
129
  'stop:guard': {
130
130
  script: 'scripts/hooks/stop-guard.ts',
131
131
  description:
132
- 'Claude Code Stop hook: deliver the three stop-time rules — hold notify/release and issue citation (reads the Stop payload on stdin)',
132
+ 'Claude Code Stop hook: send the hold notify, release the hold and print the issue citations itself; block on the rules that need the model (reads the Stop payload on stdin)',
133
133
  category: 'Development',
134
134
  reference: ['', 'automation', ['none']],
135
135
  // **No `tsx_arguments`, deliberately**, the same as `pretool:guard` above: this runs at every
@@ -19,6 +19,12 @@ const HOOKS_COMMANDS: Record<string, CommandEntry> = {
19
19
  category: 'Git hooks',
20
20
  reference: ['[paths...]', 'automation', ['none']],
21
21
  },
22
+ 'pre-commit-lint': {
23
+ script: 'scripts/hooks/pre-commit-lint.ts',
24
+ description: 'Git hook: run cspell, prettier or eslint over staged files from a content cache',
25
+ category: 'Git hooks',
26
+ reference: ['<cspell|prettier|eslint> [paths...]', 'automation', ['files']],
27
+ },
22
28
  'pre-push-unit': {
23
29
  script: 'scripts/hooks/pre-push-unit.ts',
24
30
  description: 'Git hook: run unit tests, reusing a green gate recorded on the pushed tree',
@@ -83,6 +83,15 @@ const LANE_COMMANDS: Record<string, CommandEntry> = {
83
83
  category: 'AI tools',
84
84
  reference: ['<issue>', 'developer', ['files', 'git', 'processes', 'network']],
85
85
  },
86
+ // `.env` is read for `JOSH_LANE_ROOT`, as every lane command reads it, so the lane is found.
87
+ 'lane:withdraw': {
88
+ script: 'scripts/lane/lane-withdraw-cli.ts',
89
+ tsx_arguments: OPTIONAL_ENV_FILE_FLAGS,
90
+ description:
91
+ 'Stop a running lane child, stash its uncommitted work, close its issue as not planned and close the lane — booked withdrawn, not as an outage or a failure',
92
+ category: 'AI tools',
93
+ reference: ['<issue> --reason <text>', 'developer', ['files', 'git', 'processes', 'network']],
94
+ },
86
95
  // A live run's lane limit: `.env` is read so the bare form reports the
87
96
  // `JOSH_LANE_LIMIT` a person set there when no override stands.
88
97
  'lane:limit': {
@@ -106,7 +115,7 @@ const LANE_COMMANDS: Record<string, CommandEntry> = {
106
115
  'lane:stats': {
107
116
  ...MEASURE_ARGUMENTS,
108
117
  description:
109
- 'Print one table row of lane throughput, gate duration and machine load over a period, the per-stage durations of a lane under it, and the wait after each ship stop reason under those',
118
+ 'Print one table row of lane throughput, gate duration and machine load over a period, the per-stage durations of a lane under it, the wait after each ship stop reason under those, and the duration and reuse count of each gate check last',
110
119
  category: 'AI tools',
111
120
  reference: ['--period <days> [--limit <lane-limit>]', 'maintainer', ['none']],
112
121
  default_script_arguments: ['stats'],
@@ -112,6 +112,11 @@ const WORKFLOW_COMMANDS: Record<string, CommandEntry> = {
112
112
  // A script rather than an `sh -c` chain, because the strategy has to be named rather than left to
113
113
  // the caller's git configuration: a bare `git pull` aborts with `Need to specify how to reconcile
114
114
  // divergent branches` on exactly the diverged branch the command exists for.
115
+ //
116
+ // **It declares no core weight, by measurement** (joshuafolkken/kit#3944): its own work is git, and
117
+ // the catalog it may regenerate took 0.6s of CPU. The heavy processes seen beside it are commit
118
+ // hooks — its regenerated catalog's commit runs them — which reserve themselves, and a weight here
119
+ // would set the held mark they inherit, folding the parallel hook tools into this one place.
115
120
  'main:merge': {
116
121
  script: 'scripts/git/main-merge.ts',
117
122
  description: 'Merge origin default branch into the current branch',