@joshuafolkken/kit 1.1455.0 → 1.1471.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 (211) hide show
  1. package/.claude/skills/dependency-update/SKILL.md +26 -55
  2. package/.claude/skills/epic-commands/SKILL.md +87 -148
  3. package/.claude/skills/epic-commands/epic-bundle.md +45 -77
  4. package/.claude/skills/epic-commands/execution-waves.md +44 -81
  5. package/.claude/skills/verify-ui/SKILL.md +25 -50
  6. package/.claude/skills/workflow-commands/background-commands.md +36 -47
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +72 -85
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +91 -121
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +73 -117
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +7 -9
  11. package/.claude/skills/workflow-commands/backlogrun-recovery.md +11 -20
  12. package/.claude/skills/workflow-commands/backlogrun-steps.md +43 -52
  13. package/.claude/skills/workflow-commands/chain-rule.md +24 -34
  14. package/.claude/skills/workflow-commands/followup.md +57 -76
  15. package/.claude/skills/workflow-commands/issue-comments.md +43 -60
  16. package/.claude/skills/workflow-commands/kickoff.md +65 -74
  17. package/.claude/skills/workflow-commands/observation-filing.md +62 -107
  18. package/.claude/skills/workflow-commands/observation-ledger.md +56 -111
  19. package/.claude/skills/workflow-commands/pre-gate-cut.md +89 -138
  20. package/.claude/skills/workflow-commands/prerequisite.md +10 -15
  21. package/.claude/skills/workflow-commands/progress-watcher.md +21 -26
  22. package/.claude/skills/workflow-commands/retrospective.md +31 -50
  23. package/.claude/skills/workflow-commands/split-assessment.md +34 -58
  24. package/.claude/skills/workflow-commands/working-tree-hold.md +54 -71
  25. package/CLAUDE.md +6 -5
  26. package/dist/CLAUDE.md +6 -5
  27. package/dist/hooks/batch-guard.js +6 -6
  28. package/dist/hooks/{chunk-7WQBPLUN.js → chunk-43ZHT22P.js} +31 -15
  29. package/dist/hooks/{chunk-LIAFCXL7.js → chunk-AS74OU3D.js} +23 -7
  30. package/dist/hooks/{chunk-3C4XRYG6.js → chunk-E7OIKX5Z.js} +17 -12
  31. package/dist/hooks/{chunk-Z4XI7CZS.js → chunk-EUMACUK2.js} +18 -12
  32. package/dist/hooks/{chunk-WNLVGT7E.js → chunk-H5HVWYXK.js} +4 -4
  33. package/dist/hooks/{chunk-HHI6FO3R.js → chunk-IIGFYXKC.js} +345 -240
  34. package/dist/hooks/{chunk-GPLGZ6IC.js → chunk-J47HU5H5.js} +14 -3
  35. package/dist/hooks/{chunk-OLMO72ON.js → chunk-K4EOONYM.js} +1 -1
  36. package/dist/hooks/{chunk-D5QBTOCZ.js → chunk-K4LMBQC2.js} +4 -4
  37. package/dist/hooks/{chunk-TBJFTSUY.js → chunk-KC5AK2GF.js} +8 -6
  38. package/dist/hooks/chunk-SHGPANFB.js +56 -0
  39. package/dist/hooks/{chunk-5H4SSDSD.js → chunk-YHBZAJSV.js} +3 -3
  40. package/dist/hooks/{chunk-HFGWYGUV.js → chunk-YYPI7SJZ.js} +5 -5
  41. package/dist/hooks/codex-hook-adapter.js +13 -13
  42. package/dist/hooks/duplicate-read-guard.js +7 -7
  43. package/dist/hooks/format-edited.js +3 -3
  44. package/dist/hooks/inputs.json +216 -214
  45. package/dist/hooks/investigation-guard.js +7 -7
  46. package/dist/hooks/pretool-guard.js +12 -12
  47. package/dist/hooks/session-lang.js +6 -5
  48. package/dist/hooks/stop-guard.js +18 -14
  49. package/dist/josh.js +5 -6
  50. package/dist/managed-marker/index.js +1 -2
  51. package/dist/version/index.js +15 -12
  52. package/eslint/base.js +2 -1
  53. package/eslint/rules/unicorn.js +16 -0
  54. package/package.json +3 -3
  55. package/prompts/coding-standards.md +12 -22
  56. package/prompts/collaboration-workflow/file-edits.md +26 -44
  57. package/prompts/collaboration-workflow/gh-rest.md +20 -36
  58. package/prompts/collaboration-workflow/glossary.md +5 -7
  59. package/prompts/collaboration-workflow/issue-citation.md +7 -13
  60. package/prompts/collaboration-workflow/issue-template.md +29 -29
  61. package/prompts/collaboration-workflow/operating-rules.md +35 -44
  62. package/prompts/collaboration-workflow/output-bounds.md +15 -15
  63. package/prompts/collaboration-workflow/overview.md +14 -19
  64. package/prompts/collaboration-workflow/plan-comment.md +13 -13
  65. package/prompts/collaboration-workflow/principles.md +50 -53
  66. package/prompts/collaboration-workflow/proposal-request.md +1 -9
  67. package/prompts/collaboration-workflow/report-format.md +32 -49
  68. package/prompts/collaboration-workflow/residency.md +10 -10
  69. package/prompts/collaboration-workflow/rule-delivery.md +9 -9
  70. package/prompts/collaboration-workflow/shell-body.md +10 -20
  71. package/prompts/collaboration-workflow/turn-batching.md +8 -14
  72. package/prompts/collaboration-workflow/upstream-interrupt.md +26 -34
  73. package/prompts/collaboration-workflow/wip-cap.md +24 -30
  74. package/prompts/collaboration-workflow.md +24 -25
  75. package/prompts/refactoring.md +33 -43
  76. package/prompts/review-rubric.md +84 -129
  77. package/prompts/review.md +111 -141
  78. package/prompts/sonar-hotspot-handling.md +22 -50
  79. package/prompts/testing-guide.md +48 -99
  80. package/scripts/adopt/adopt-toolkits.ts +2 -4
  81. package/scripts/agent/agent-exit-record.ts +23 -1
  82. package/scripts/agent/codex-agent-argv.ts +7 -2
  83. package/scripts/backlog/backlog-budget-cli.ts +1 -2
  84. package/scripts/backlog/backlog-drive-carried.ts +26 -0
  85. package/scripts/backlog/backlog-drive-cli.ts +10 -5
  86. package/scripts/backlog/backlog-drive-named.ts +2 -4
  87. package/scripts/backlog/backlog-drive-restore.ts +19 -1
  88. package/scripts/backlog/backlog-drive-seats.ts +27 -0
  89. package/scripts/backlog/backlog-drive.ts +11 -6
  90. package/scripts/backlog/backlog-ready.ts +13 -6
  91. package/scripts/behavior/behavior-rules.ts +3 -2
  92. package/scripts/cost-runtime/codex-usage.ts +4 -2
  93. package/scripts/cost-runtime/cost-cli.ts +1 -2
  94. package/scripts/delegation/investigation-guard.ts +1 -1
  95. package/scripts/delegation/investigation-reads.ts +1 -1
  96. package/scripts/discovery/repo-discovery.ts +4 -2
  97. package/scripts/discovery/repo-origin.ts +2 -4
  98. package/scripts/doctor/doctor-logic.ts +6 -3
  99. package/scripts/document/document-byte-budget.ts +27 -27
  100. package/scripts/document/document-byte-check.ts +1 -2
  101. package/scripts/document/entry-read-budget.ts +2 -2
  102. package/scripts/epic/epic-audit-rationale.ts +8 -2
  103. package/scripts/epic/epic-bundle-cli.ts +1 -2
  104. package/scripts/epic/epic-chains.ts +6 -2
  105. package/scripts/epic/epic-close-comment.ts +1 -2
  106. package/scripts/gate/verification-gate.ts +7 -2
  107. package/scripts/gh/git-pr.ts +6 -2
  108. package/scripts/git/git-common-directory.ts +3 -3
  109. package/scripts/git/git-diff-reads.ts +16 -9
  110. package/scripts/git/git-execa-error.ts +1 -2
  111. package/scripts/hooks/format-edited-file.ts +10 -4
  112. package/scripts/hooks/pre-push-unit.ts +15 -4
  113. package/scripts/hooks/pretool-guard.ts +23 -29
  114. package/scripts/hooks/step-zero-notice.ts +3 -2
  115. package/scripts/init/hook-command-rewrite.ts +6 -2
  116. package/scripts/init/init-logic-deploy-vps.ts +4 -2
  117. package/scripts/issue/behavior-change-lint.ts +12 -6
  118. package/scripts/issue/issue-scout-cli.ts +3 -2
  119. package/scripts/josh/file-map-stamp.ts +1 -3
  120. package/scripts/josh/hook-decision.ts +17 -2
  121. package/scripts/lane/lane-await.ts +3 -2
  122. package/scripts/lane/lane-child-invocation.ts +14 -1
  123. package/scripts/lane/lane-guard-policy.ts +4 -4
  124. package/scripts/lane/lane-ledger.ts +9 -0
  125. package/scripts/lane/lane-load.ts +9 -2
  126. package/scripts/lane/lane-measure-cli.ts +2 -0
  127. package/scripts/lane/lane-parked.ts +18 -0
  128. package/scripts/lane/lane-process-load.ts +125 -0
  129. package/scripts/lane/lane-process-stats.ts +93 -0
  130. package/scripts/lane/lane-registry.ts +8 -3
  131. package/scripts/lane/lane-session-broker.ts +29 -5
  132. package/scripts/lane/lane-spent.ts +14 -3
  133. package/scripts/lane/lane-stats.ts +1 -0
  134. package/scripts/managed-marker/managed-marker-logic.ts +1 -2
  135. package/scripts/overrides/lockfile-overrides.ts +1 -3
  136. package/scripts/propagate/propagate.ts +1 -2
  137. package/scripts/release/release-progress.ts +3 -2
  138. package/scripts/report/report-lint.ts +7 -4
  139. package/scripts/review/review-record-cli.ts +6 -2
  140. package/scripts/rules/destructive-command.ts +6 -9
  141. package/scripts/rules/direct-filing.ts +3 -7
  142. package/scripts/rules/early-heartbeat.ts +7 -18
  143. package/scripts/rules/file-body.ts +6 -13
  144. package/scripts/rules/filing-cap.ts +3 -6
  145. package/scripts/rules/git-force.ts +5 -9
  146. package/scripts/rules/implementation-cut.ts +37 -35
  147. package/scripts/rules/index-guard.ts +4 -7
  148. package/scripts/rules/issue-comments.ts +5 -15
  149. package/scripts/rules/josh-git-bare.ts +3 -6
  150. package/scripts/rules/lane-background.ts +10 -21
  151. package/scripts/rules/lane-carry-conflict.ts +7 -17
  152. package/scripts/rules/lane-interactive-ask.ts +6 -11
  153. package/scripts/rules/lane-park.ts +5 -13
  154. package/scripts/rules/lane-split-park.ts +2 -7
  155. package/scripts/rules/lane-switch-main.ts +9 -12
  156. package/scripts/rules/oracle-consulted.ts +4 -5
  157. package/scripts/rules/piped-verification.ts +81 -31
  158. package/scripts/rules/poll-loop.ts +7 -17
  159. package/scripts/rules/pre-gate-cut.ts +7 -21
  160. package/scripts/rules/prior-comment-read.ts +7 -5
  161. package/scripts/rules/protected-files.ts +5 -9
  162. package/scripts/rules/raw-field-body.ts +3 -7
  163. package/scripts/rules/rule-body-guard.ts +4 -11
  164. package/scripts/rules/run-tail-rule.ts +6 -22
  165. package/scripts/rules/shell-body-trigger.ts +4 -6
  166. package/scripts/rules/shell-segments.ts +7 -0
  167. package/scripts/rules/stop-rules.ts +16 -29
  168. package/scripts/rules/test-declared-commit.ts +4 -10
  169. package/scripts/rules/third-party-write.ts +5 -10
  170. package/scripts/rules/worktree-guard.ts +5 -12
  171. package/scripts/run/board/run-board-github.ts +11 -3
  172. package/scripts/run/board/run-board-idle.ts +31 -0
  173. package/scripts/run/board/run-board-read.ts +9 -12
  174. package/scripts/run/board/run-board-reason.ts +7 -3
  175. package/scripts/run/board/run-board-state.ts +4 -0
  176. package/scripts/run/board/run-board-status.ts +18 -3
  177. package/scripts/run/board/run-board-thanks.ts +7 -2
  178. package/scripts/run/board/run-board-tick.ts +32 -7
  179. package/scripts/run/carry/run-carry-args.ts +1 -3
  180. package/scripts/run/carry/run-carry-conversation.ts +7 -2
  181. package/scripts/run/cut/run-cut-cli.ts +15 -20
  182. package/scripts/run/cut/run-cut-decline.ts +72 -0
  183. package/scripts/run/cut/run-cut-ready.ts +74 -0
  184. package/scripts/run/cut/run-cut-report.ts +13 -0
  185. package/scripts/run/cut/run-cut.ts +10 -1
  186. package/scripts/run/hold/run-hold-cli.ts +14 -3
  187. package/scripts/run/hold/run-hold.ts +6 -3
  188. package/scripts/run/merge/run-merge-cli.ts +30 -7
  189. package/scripts/run/merge/run-merge-ship.ts +59 -1
  190. package/scripts/run/merge/run-merge.ts +3 -2
  191. package/scripts/run/run-ending.ts +31 -3
  192. package/scripts/run/run-parent-cut-hook.ts +5 -10
  193. package/scripts/run/run-preflight.ts +1 -2
  194. package/scripts/run/run-stop-notify.ts +1 -2
  195. package/scripts/run/run-watcher-guard.ts +6 -3
  196. package/scripts/run/ship/run-ship-review-steps.ts +13 -4
  197. package/scripts/run/ship/run-ship-review.ts +15 -2
  198. package/scripts/run/tidy/run-tidy-cli.ts +3 -3
  199. package/scripts/run/tidy/run-tidy-lanes.ts +3 -2
  200. package/scripts/safe-chain/project-config.ts +7 -2
  201. package/scripts/sync/sync-hook-safety.ts +8 -2
  202. package/scripts/test/unit-worker-share.ts +3 -2
  203. package/scripts/time-runtime/time-batch-guard.ts +3 -3
  204. package/scripts/time-runtime/time-hook-transcript.ts +3 -2
  205. package/scripts/time-runtime/time-shell.ts +10 -0
  206. package/scripts/version/effective-upstream.ts +3 -4
  207. package/scripts/version/kit-version-config.ts +8 -2
  208. package/scripts/version/latest-corepack.ts +1 -2
  209. package/scripts/version/release-hold.ts +11 -4
  210. package/scripts/version/version-commands.ts +12 -4
  211. package/dist/hooks/chunk-UT7YC3N2.js +0 -56
@@ -53,8 +53,9 @@ const INDEX_ASSERTION_NAME = 'no-direct-git-index-mutation'
53
53
  // a tool result, or a call to any tool but `Bash`. Read through `json_value.is_record` so a malformed
54
54
  // input is treated as absent rather than throwing mid-scan.
55
55
  function command_of(block: Block): string | undefined {
56
- if (block.type !== TOOL_USE || block.name !== BASH_TOOL) return undefined
57
- if (!json_value.is_record(block.input)) return undefined
56
+ if (block.type !== TOOL_USE || block.name !== BASH_TOOL || !json_value.is_record(block.input)) {
57
+ return undefined
58
+ }
58
59
 
59
60
  const command = block.input[COMMAND_KEY]
60
61
 
@@ -56,8 +56,10 @@ function measurement_from(
56
56
  return parsed.success ? [parsed.data.payload.info] : []
57
57
  })
58
58
  const session = metadata.find((entry) => entry.payload.id === thread_id)
59
- if (session === undefined || usages.length === 0) return undefined
60
- if (!same_project(session.payload.cwd, target)) return undefined
59
+
60
+ if (session === undefined || usages.length === 0 || !same_project(session.payload.cwd, target)) {
61
+ return undefined
62
+ }
61
63
 
62
64
  return { billed_input_per_request: billed_per_request(usages) }
63
65
  }
@@ -56,8 +56,7 @@ interface RunContext {
56
56
  // `--over=` does not arrive as "hand off after any request" and stop an unattended run after its
57
57
  // first child.
58
58
  function to_threshold(raw: string | undefined): number | undefined {
59
- if (raw === undefined) return undefined
60
- if (raw.trim() === '') return undefined
59
+ if (raw === undefined || raw.trim() === '') return undefined
61
60
 
62
61
  const parsed = Number(raw)
63
62
 
@@ -69,7 +69,7 @@ function should_notify(
69
69
  // A dispatched lane child ends its turn on a denial, so
70
70
  // the same guidance is carried without a `permissionDecision`: the read proceeds and the child is nudged
71
71
  // to delegate rather than killed. It carries no ⛔, so a person watching reads it as advice.
72
- const NOTICE = `💡 investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold, so the reading from here should go to a unit of its own. This is a notice, not a refusal — the read proceeds, because a dispatched lane child ends its turn on a denial. Ask \`pnpm josh delegate investigation\`, then dispatch the \`${investigation_reads.INVESTIGATOR_AGENT}\` agent (\`kit:${investigation_reads.INVESTIGATOR_AGENT}\` in a consumer) with a brief of what the main line has already concluded and what is left to find out; it returns the conclusion plus its \`file:line\` citations, never the file text. Keep a read in the main line only where this run will edit that file. This recurs on the next accumulation.`
72
+ const NOTICE = `💡 investigation: files read and not edited, or search turns, since the last delegated unit reached the threshold; the read proceeds. Run \`pnpm josh delegate investigation\`, then dispatch the \`${investigation_reads.INVESTIGATOR_AGENT}\` agent (\`kit:${investigation_reads.INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text. Keep main-line reads to files you will edit.`
73
73
 
74
74
  // The notice as it reaches the model: the base guidance with the concrete unedited reads named, so a
75
75
  // lane child is shown *which* reads to send to a unit rather than only told to delegate. An empty
@@ -42,7 +42,7 @@ const SEARCH_LABELS: ReadonlySet<string> = new Set(
42
42
  const INVESTIGATOR_AGENT = 'investigator'
43
43
 
44
44
  // The deny reason is the only text that reaches the model, so it names the count, command and return shape.
45
- const REASON = `⛔ investigation: ${String(delegation_policy.INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit, so the reading from here goes to a unit of its own (a successful write also starts the search count over). Ask \`pnpm josh delegate investigation\`, then dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer) with a brief of what the main line has already concluded and what is left to find out; it returns the conclusion plus its \`file:line\` citations, never the file text. Keep a read in the main line only where this run will edit that file — an \`Edit\` cannot be issued against text you do not hold. This run's own instructions and the harness's own session files — a backgrounded call's output, a persisted tool result, this session's scratchpad — never count toward the files, so they are not what took the file count here; every read-only search turn counts, whatever it names. The rule is \`.claude/skills/workflow-commands/delegation.md\` → "The pre-implementation reading".`
45
+ const REASON = `⛔ investigation: ${String(delegation_policy.INVESTIGATION_FILE_THRESHOLD)} files read and not edited, or ${String(delegation_policy.INVESTIGATION_SEARCH_TURN_THRESHOLD)} search turns, since the last delegated unit. Run \`pnpm josh delegate investigation\`, then dispatch the \`${INVESTIGATOR_AGENT}\` agent (\`kit:${INVESTIGATOR_AGENT}\` in a consumer); it returns \`file:line\` citations, never the file text. Keep main-line reads to files you will edit; every read-only search turn counts. \`.claude/skills/workflow-commands/delegation.md\``
46
46
 
47
47
  interface ReadTally {
48
48
  // The files read and not since edited, resolved.
@@ -138,8 +138,10 @@ function resolve_current_owner(repository_path: string): string | undefined {
138
138
  // all — the very failure this function exists to close, moved somewhere else.
139
139
  function main_worktree(repository_path: string): string {
140
140
  const git_directory = resolve_git_directory(repository_path)
141
- if (git_directory === undefined) return repository_path
142
- if (path.basename(git_directory) !== GIT_DIRECTORY) return repository_path
141
+
142
+ if (git_directory === undefined || path.basename(git_directory) !== GIT_DIRECTORY) {
143
+ return repository_path
144
+ }
143
145
 
144
146
  return path.dirname(git_directory)
145
147
  }
@@ -68,8 +68,7 @@ function parse_scp_like(url: string): RepoIdentity | undefined {
68
68
  const user_end = url.indexOf(USER_SEPARATOR)
69
69
  const authority = user_end === NOT_FOUND ? url : url.slice(user_end + 1)
70
70
  const host_end = authority.indexOf(HOST_SEPARATOR)
71
- if (host_end === NOT_FOUND) return undefined
72
- if (!is_github_ssh_host(authority.slice(0, host_end))) return undefined
71
+ if (host_end === NOT_FOUND || !is_github_ssh_host(authority.slice(0, host_end))) return undefined
73
72
 
74
73
  return to_identity(authority.slice(host_end + 1))
75
74
  }
@@ -115,8 +114,7 @@ function origin_section_lines(lines: ReadonlyArray<string>): Array<string> {
115
114
  // the first separator rather than matched, for the reason `parse_scp_like` is split by hand.
116
115
  function url_value(line: string): string | undefined {
117
116
  const separator = line.indexOf('=')
118
- if (separator === NOT_FOUND) return undefined
119
- if (line.slice(0, separator).trim() !== URL_KEY) return undefined
117
+ if (separator === NOT_FOUND || line.slice(0, separator).trim() !== URL_KEY) return undefined
120
118
 
121
119
  return line.slice(separator + 1).trim()
122
120
  }
@@ -69,9 +69,12 @@ function decide_reclaim(
69
69
  global_josh: string | undefined,
70
70
  shim_content: string,
71
71
  ): ReclaimDecision {
72
- if (!is_shadowed(path_josh, global_josh)) return { action: 'none', reason: NO_SHADOW_REASON }
73
- // is_shadowed already guarantees path_josh is defined; this narrows the type for the return.
74
- if (path_josh === undefined) return { action: 'none', reason: NO_SHADOW_REASON }
72
+ // is_shadowed already implies path_josh is defined; the undefined check narrows the type for the
73
+ // return.
74
+ if (path_josh === undefined || !is_shadowed(path_josh, global_josh)) {
75
+ return { action: 'none', reason: NO_SHADOW_REASON }
76
+ }
77
+
75
78
  if (!is_kit_shim(shim_content)) return { action: 'none', reason: NOT_KIT_SHIM_REASON }
76
79
 
77
80
  return { action: 'remove', target: path_josh, reason: RECLAIM_REASON }
@@ -57,52 +57,52 @@ function block_ceiling(size: number): number {
57
57
  // a stale entry (a file that no longer exists or that an entry now reads) and on an un-budgeted
58
58
  // unreached file, so the definition cannot rot as documents move on or off the execution path.
59
59
  const DOCUMENT_BYTE_BUDGET: ReadonlyArray<DocumentBudget> = [
60
- { path: '.claude/skills/dependency-update/SKILL.md', bytes: 8192 },
61
- { path: '.claude/skills/epic-commands/SKILL.md', bytes: 16_384 },
62
- { path: '.claude/skills/epic-commands/epic-bundle.md', bytes: 8192 },
63
- { path: '.claude/skills/epic-commands/execution-waves.md', bytes: 8192 },
64
- { path: '.claude/skills/verify-ui/SKILL.md', bytes: 8192 },
60
+ { path: '.claude/skills/dependency-update/SKILL.md', bytes: 4096 },
61
+ { path: '.claude/skills/epic-commands/SKILL.md', bytes: 12_288 },
62
+ { path: '.claude/skills/epic-commands/epic-bundle.md', bytes: 4096 },
63
+ { path: '.claude/skills/epic-commands/execution-waves.md', bytes: 4096 },
64
+ { path: '.claude/skills/verify-ui/SKILL.md', bytes: 4096 },
65
65
  { path: '.claude/skills/workflow-commands/backlogrun-handoff-report.md', bytes: 4096 },
66
66
  { path: '.claude/skills/workflow-commands/fullrun-steps.md', bytes: 8192 },
67
67
  { path: '.claude/skills/workflow-commands/into-target.md', bytes: 4096 },
68
68
  { path: '.claude/skills/workflow-commands/issue-comments.md', bytes: 8192 },
69
69
  { path: '.claude/skills/workflow-commands/issue-fold-existing.md', bytes: 4096 },
70
70
  { path: '.claude/skills/workflow-commands/needs-human-review.md', bytes: 4096 },
71
- { path: '.claude/skills/workflow-commands/observation-filing.md', bytes: 12_288 },
72
- { path: '.claude/skills/workflow-commands/observation-ledger.md', bytes: 12_288 },
71
+ { path: '.claude/skills/workflow-commands/observation-filing.md', bytes: 8192 },
72
+ { path: '.claude/skills/workflow-commands/observation-ledger.md', bytes: 8192 },
73
73
  { path: '.claude/skills/workflow-commands/prerequisite.md', bytes: 8192 },
74
74
  { path: '.claude/skills/workflow-commands/target-repository.md', bytes: 4096 },
75
75
  { path: '.claude/skills/workflow-commands/working-tree-hold.md', bytes: 8192 },
76
- { path: 'docs/josh-commands-automation.md', bytes: 53_248 },
77
- { path: 'docs/josh-commands-run.md', bytes: 77_824 },
78
- { path: 'docs/josh-commands-backlog.md', bytes: 61_440 },
79
- { path: 'docs/josh-commands.md', bytes: 57_344 },
76
+ { path: 'docs/josh-commands-automation.md', bytes: 45_056 },
77
+ { path: 'docs/josh-commands-run.md', bytes: 61_440 },
78
+ { path: 'docs/josh-commands-backlog.md', bytes: 49_152 },
79
+ { path: 'docs/josh-commands.md', bytes: 45_056 },
80
80
  { path: 'docs/labels-and-run-states.md', bytes: 8192 },
81
- { path: 'prompts/coding-standards.md', bytes: 8192 },
82
- { path: 'prompts/collaboration-workflow.md', bytes: 8192 },
83
- { path: 'prompts/collaboration-workflow/file-edits.md', bytes: 12_288 },
84
- { path: 'prompts/collaboration-workflow/gh-rest.md', bytes: 8192 },
81
+ { path: 'prompts/coding-standards.md', bytes: 4096 },
82
+ { path: 'prompts/collaboration-workflow.md', bytes: 4096 },
83
+ { path: 'prompts/collaboration-workflow/file-edits.md', bytes: 8192 },
84
+ { path: 'prompts/collaboration-workflow/gh-rest.md', bytes: 4096 },
85
85
  { path: 'prompts/collaboration-workflow/glossary.md', bytes: 4096 },
86
86
  { path: 'prompts/collaboration-workflow/issue-citation.md', bytes: 4096 },
87
- { path: 'prompts/collaboration-workflow/issue-template.md', bytes: 12_288 },
88
- { path: 'prompts/collaboration-workflow/operating-rules.md', bytes: 12_288 },
87
+ { path: 'prompts/collaboration-workflow/issue-template.md', bytes: 8192 },
88
+ { path: 'prompts/collaboration-workflow/operating-rules.md', bytes: 8192 },
89
89
  { path: 'prompts/collaboration-workflow/output-bounds.md', bytes: 4096 },
90
90
  { path: 'prompts/collaboration-workflow/overview.md', bytes: 4096 },
91
- { path: 'prompts/collaboration-workflow/plan-comment.md', bytes: 8192 },
92
- { path: 'prompts/collaboration-workflow/principles.md', bytes: 16_384 },
91
+ { path: 'prompts/collaboration-workflow/plan-comment.md', bytes: 4096 },
92
+ { path: 'prompts/collaboration-workflow/principles.md', bytes: 12_288 },
93
93
  { path: 'prompts/collaboration-workflow/proposal-request.md', bytes: 4096 },
94
- { path: 'prompts/collaboration-workflow/report-format.md', bytes: 16_384 },
94
+ { path: 'prompts/collaboration-workflow/report-format.md', bytes: 12_288 },
95
95
  { path: 'prompts/collaboration-workflow/residency.md', bytes: 8192 },
96
96
  { path: 'prompts/collaboration-workflow/rule-delivery.md', bytes: 4096 },
97
- { path: 'prompts/collaboration-workflow/shell-body.md', bytes: 8192 },
98
- { path: 'prompts/collaboration-workflow/turn-batching.md', bytes: 8192 },
99
- { path: 'prompts/collaboration-workflow/upstream-interrupt.md', bytes: 12_288 },
100
- { path: 'prompts/collaboration-workflow/wip-cap.md', bytes: 12_288 },
97
+ { path: 'prompts/collaboration-workflow/shell-body.md', bytes: 4096 },
98
+ { path: 'prompts/collaboration-workflow/turn-batching.md', bytes: 4096 },
99
+ { path: 'prompts/collaboration-workflow/upstream-interrupt.md', bytes: 8192 },
100
+ { path: 'prompts/collaboration-workflow/wip-cap.md', bytes: 8192 },
101
101
  { path: 'prompts/refactoring.md', bytes: 8192 },
102
- { path: 'prompts/review-rubric.md', bytes: 16_384 },
103
- { path: 'prompts/review.md', bytes: 16_384 },
102
+ { path: 'prompts/review-rubric.md', bytes: 12_288 },
103
+ { path: 'prompts/review.md', bytes: 12_288 },
104
104
  { path: 'prompts/sonar-hotspot-handling.md', bytes: 8192 },
105
- { path: 'prompts/testing-guide.md', bytes: 16_384 },
105
+ { path: 'prompts/testing-guide.md', bytes: 12_288 },
106
106
  ]
107
107
 
108
108
  // The recorded ceiling for one path, or undefined when it carries no budget entry.
@@ -26,8 +26,7 @@ function ceiling_message(
26
26
  current_bytes: number,
27
27
  recorded_bytes: number | undefined,
28
28
  ): string | undefined {
29
- if (recorded_bytes === undefined) return undefined
30
- if (current_bytes <= recorded_bytes) return undefined
29
+ if (recorded_bytes === undefined || current_bytes <= recorded_bytes) return undefined
31
30
 
32
31
  return document_byte_budget.over_budget_message(relative_path, current_bytes, recorded_bytes)
33
32
  }
@@ -48,8 +48,8 @@ const ENTRY_READ_BUDGET: ReadonlyArray<EntryBudget> = [
48
48
  { entry: 'fullrun', bytes: 53_248 },
49
49
  { entry: 'halfrun', bytes: 49_152 },
50
50
  { entry: 'prrun', bytes: 57_344 },
51
- { entry: 'backlogrun', bytes: 90_112 },
52
- { entry: LANE_CHILD, bytes: 49_152 },
51
+ { entry: 'backlogrun', bytes: 86_016 },
52
+ { entry: LANE_CHILD, bytes: 45_056 },
53
53
  ]
54
54
 
55
55
  function byte_size(root: string, relative_path: string): number {
@@ -68,9 +68,15 @@ function order_pairs(
68
68
  return links.flatMap((link) => {
69
69
  const blocker = find_child(children, { repo: epic_repo, number: link.blocker })
70
70
  const blocked = find_child(children, { repo: epic_repo, number: link.blocked })
71
- if (blocker === undefined || blocked === undefined) return []
72
71
 
73
- if (!is_open_local(blocker, current_repo) || !is_open_local(blocked, current_repo)) return []
72
+ if (
73
+ blocker === undefined ||
74
+ blocked === undefined ||
75
+ !is_open_local(blocker, current_repo) ||
76
+ !is_open_local(blocked, current_repo)
77
+ ) {
78
+ return []
79
+ }
74
80
 
75
81
  // eslint-disable-next-line unicorn/no-unnecessary-array-flat-map -- one return type (sonarjs/function-return-type)
76
82
  return [{ blocker, blocked }]
@@ -323,12 +323,11 @@ async function widen_with_referenced(
323
323
  backlog: FetchedBacklog,
324
324
  ): Promise<FetchedBacklog> {
325
325
  const { context } = backlog
326
- if (context === undefined) return backlog
327
326
  // An issue an epic already tracks has nothing to bundle — `decide_bundle` answers from that alone
328
327
  // and never looks at the candidates. Widening first spends a request per reference on a verdict
329
328
  // that ignores them, and prints "⚠ Could not read #891." above "Already in an epic", which reads
330
329
  // as a gap in an answer the read was never part of.
331
- if (subject.epic !== undefined) return backlog
330
+ if (context === undefined || subject.epic !== undefined) return backlog
332
331
  const known = new Set(backlog.issues.map((issue) => issue.number))
333
332
  const found = await epic_bundle_referenced.referenced_candidates(subject, known, context)
334
333
  if (found === undefined) return backlog
@@ -190,8 +190,12 @@ function find_position_ambiguity(
190
190
  chains: Chains,
191
191
  position: InsertPosition | undefined,
192
192
  ): string | undefined {
193
- if (position?.kind !== 'before') return undefined
194
- if (chains_containing(chains, position.target).length < AMBIGUOUS_MATCH_COUNT) return undefined
193
+ if (
194
+ position?.kind !== 'before' ||
195
+ chains_containing(chains, position.target).length < AMBIGUOUS_MATCH_COUNT
196
+ ) {
197
+ return undefined
198
+ }
195
199
 
196
200
  return to_ambiguous_position_error(position)
197
201
  }
@@ -66,8 +66,7 @@ function marker_children(line: string): Array<string> | undefined {
66
66
  if (indent_of(line) >= INDENTED_CODE_SPACES) return undefined
67
67
 
68
68
  const trimmed = line.trim()
69
- if (!trimmed.startsWith(MARKER_OPEN)) return undefined
70
- if (!trimmed.endsWith(MARKER_CLOSE)) return undefined
69
+ if (!trimmed.startsWith(MARKER_OPEN) || !trimmed.endsWith(MARKER_CLOSE)) return undefined
71
70
 
72
71
  const listed = trimmed.slice(MARKER_OPEN.length, -MARKER_CLOSE.length)
73
72
 
@@ -328,8 +328,13 @@ interface GateOptions {
328
328
  // `--force` is the caller saying to run regardless. `scoped_green` owns the record and the `JOSH_SCOPED_GREEN`
329
329
  // escape hatch, so a person can always get past a record that is wrong about their tree.
330
330
  function scoped_precheck_refusal(tree: GateTree, options: GateOptions): string | undefined {
331
- if (options.is_scoped_enforced !== true) return undefined
332
- if (options.is_forced === true || options.is_unit_included === false) return undefined
331
+ if (
332
+ options.is_scoped_enforced !== true ||
333
+ options.is_forced === true ||
334
+ options.is_unit_included === false
335
+ ) {
336
+ return undefined
337
+ }
333
338
 
334
339
  return scoped_green.refusal_for(tree.files, tree.base, options.scoped_sources)
335
340
  }
@@ -173,8 +173,12 @@ function build_body(issue_info: IssueInfo, extra_body?: string): string {
173
173
  }
174
174
 
175
175
  async function existing_pr_label(branch_name: string): Promise<ReleaseClassification | undefined> {
176
- if (!(await git_gh_command.pr_exists(branch_name))) return undefined
177
- if (is_pr_state_merged(await get_pr_state(branch_name))) return undefined
176
+ if (
177
+ !(await git_gh_command.pr_exists(branch_name)) ||
178
+ is_pr_state_merged(await get_pr_state(branch_name))
179
+ ) {
180
+ return undefined
181
+ }
178
182
 
179
183
  return await git_gh_command.pr_get_classification(branch_name)
180
184
  }
@@ -88,9 +88,9 @@ function select_linked(found: ReadonlyArray<string>): string | undefined {
88
88
  const own_directory = found[OWN_GIT_DIRECTORY]
89
89
  const common = found[COMMON_GIT_DIRECTORY]
90
90
 
91
- return own_directory !== undefined && common !== undefined && own_directory !== common
92
- ? common
93
- : undefined
91
+ return own_directory === undefined || common === undefined || own_directory === common
92
+ ? undefined
93
+ : common
94
94
  }
95
95
 
96
96
  function resolve(cwd: string): string | undefined {
@@ -9,19 +9,24 @@ const REFS_REMOTES_ORIGIN_PREFIX = 'refs/remotes/origin/'
9
9
  const NAME_ONLY_FLAG = '--name-only'
10
10
  const DEFAULT_BRANCH_FALLBACK = 'main'
11
11
 
12
+ const DEFAULT_BRANCH_ARGUMENTS = ['symbolic-ref', 'refs/remotes/origin/HEAD'] as const
13
+
14
+ // The default branch `DEFAULT_BRANCH_ARGUMENTS` names, or the fallback for a missing or unexpected
15
+ // answer — shared with the synchronous read the implementation-cut guard takes it with.
16
+ function default_branch_of(output: string | undefined): string {
17
+ const trimmed = output?.trim() ?? ''
18
+
19
+ if (!trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) return DEFAULT_BRANCH_FALLBACK
20
+
21
+ return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length)
22
+ }
23
+
12
24
  async function get_default_branch(): Promise<string> {
13
25
  try {
14
- const output = await git_spawn.read(['symbolic-ref', 'refs/remotes/origin/HEAD'])
15
- const trimmed = output.trim()
16
-
17
- if (trimmed.startsWith(REFS_REMOTES_ORIGIN_PREFIX)) {
18
- return trimmed.slice(REFS_REMOTES_ORIGIN_PREFIX.length)
19
- }
26
+ return default_branch_of(await git_spawn.read([...DEFAULT_BRANCH_ARGUMENTS]))
20
27
  } catch {
21
- // fall through to default
28
+ return DEFAULT_BRANCH_FALLBACK
22
29
  }
23
-
24
- return DEFAULT_BRANCH_FALLBACK
25
30
  }
26
31
 
27
32
  const MERGE_BASE_COMMAND = 'merge-base'
@@ -210,8 +215,10 @@ async function untracked_names(): Promise<string> {
210
215
  }
211
216
 
212
217
  const git_diff_reads = {
218
+ DEFAULT_BRANCH_ARGUMENTS,
213
219
  change_base,
214
220
  change_base_commit,
221
+ default_branch_of,
215
222
  default_branch_reference,
216
223
  diff_cached_names,
217
224
  diff_main,
@@ -1,8 +1,7 @@
1
1
  // Extract the exit code from a thrown execa error. Returns the numeric exit code,
2
2
  // or undefined when the process was terminated by a signal or failed to spawn.
3
3
  function get_exit_code(error: unknown): number | undefined {
4
- if (typeof error !== 'object' || error === null) return undefined
5
- if (!('exitCode' in error)) return undefined
4
+ if (typeof error !== 'object' || error === null || !('exitCode' in error)) return undefined
6
5
 
7
6
  const { exitCode: exit_code } = error
8
7
 
@@ -251,8 +251,13 @@ function plan_daemon_restart(
251
251
  ): ReadonlyArray<FormatCommand> {
252
252
  const relative = relative_to_root(project_root, file_path)
253
253
 
254
- if (relative === undefined || !is_eslint_config_input(relative)) return []
255
- if (resolve_daemon_cli(ESLINT_DAEMON, project_root) === undefined) return []
254
+ if (
255
+ relative === undefined ||
256
+ !is_eslint_config_input(relative) ||
257
+ resolve_daemon_cli(ESLINT_DAEMON, project_root) === undefined
258
+ ) {
259
+ return []
260
+ }
256
261
 
257
262
  return [{ bin: ESLINT_DAEMON, command_arguments: [DAEMON_RESTART] }]
258
263
  }
@@ -325,8 +330,9 @@ async function run_command(command: FormatCommand, project_root: string): Promis
325
330
  // something to say. A start failure (non-zero, empty) has already been retried and carries no
326
331
  // problems, and prettier's stdout is the list of files it wrote rather than anything to act on.
327
332
  function eslint_unfixed(command: FormatCommand, outcome: CommandOutcome): string | undefined {
328
- if (command.bin !== ESLINT_BIN) return undefined
329
- if (outcome.exit_code === 0 || outcome.stdout.length === 0) return undefined
333
+ if (command.bin !== ESLINT_BIN || outcome.exit_code === 0 || outcome.stdout.length === 0) {
334
+ return undefined
335
+ }
330
336
 
331
337
  return outcome.stdout
332
338
  }
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
+ import { core_budget } from '#scripts/gate/core-budget'
4
+ import { gate_plan } from '#scripts/gate/gate-plan'
3
5
  import { gate_skip, type GateReuse } from '#scripts/gate/gate-skip'
4
6
  import { gate_tree, type GateTree } from '#scripts/gate/gate-tree'
5
7
  import { hook_gate_reuse } from '#scripts/gate/hook-gate-reuse'
@@ -123,6 +125,18 @@ function format_miss(reason: string): string {
123
125
  return `↻ running the whole unit suite — the green gate record does not cover this push: ${reason}.`
124
126
  }
125
127
 
128
+ // **The whole suite claims its place in the core budget, the one `josh test:unit` claims.** Without it
129
+ // a push's suite ran beside other lanes' gates off the ledger, so their admission saw its workers only
130
+ // as foreign load (joshuafolkken/kit#3874). Only the miss reserves: a reused record runs nothing, and a
131
+ // claim there could make a push wait out a full ledger for a suite it then skips.
132
+ async function run_reserved_suite(vitest_arguments: ReadonlyArray<string>): Promise<number> {
133
+ return await core_budget.with_command_reservation(
134
+ gate_plan.direct_unit_weight(),
135
+ async () => await test_unit_guard.run_guarded_unit(process.cwd(), vitest_arguments),
136
+ { memory_mb: gate_plan.direct_unit_memory() },
137
+ )
138
+ }
139
+
126
140
  async function run_pre_push_unit(
127
141
  extra_arguments: ReadonlyArray<string> = [],
128
142
  source?: string,
@@ -132,10 +146,7 @@ async function run_pre_push_unit(
132
146
  if ('miss' in reuse) {
133
147
  process.stdout.write(`${format_miss(reuse.miss)}\n`)
134
148
 
135
- return await test_unit_guard.run_guarded_unit(
136
- process.cwd(),
137
- forwarded_arguments(extra_arguments),
138
- )
149
+ return await run_reserved_suite(forwarded_arguments(extra_arguments))
139
150
  }
140
151
 
141
152
  process.stdout.write(`${format_skip(reuse.stamp.taken_at)}\n`)
@@ -1,5 +1,5 @@
1
1
  import { duplicate_read_outcome } from '#scripts/delegation/duplicate-read-guard'
2
- import { investigation_refusal } from '#scripts/delegation/investigation-guard'
2
+ import { investigation_outcome } from '#scripts/delegation/investigation-guard'
3
3
  import { issue_watch_hook } from '#scripts/issue/issue-watch-hook'
4
4
  import { hook_decision, type GuardOutcome } from '#scripts/josh/hook-decision'
5
5
  import { lane_phase } from '#scripts/lane/lane-phase'
@@ -25,35 +25,30 @@ import { step_zero_notice } from './step-zero-notice'
25
25
  // union matcher (`Bash|Edit|Read|Write|AskUserQuestion`) refuses exactly what the three separate
26
26
  // entries did — `AskUserQuestion` is matched for the rule guard's lane-child interactive-ask row,
27
27
  // and the batching and investigation guards self-gate away from it. A
28
- // refusal from any guard wins first; only when none refuses is the batch guard's non-blocking notice
29
- // (the whole-file-write notice) emitted. Each guard still honours its own
28
+ // refusal from any guard wins first; only when none refuses is a guard's non-blocking notice or fault
29
+ // (the whole-file-write notice, a lane child's investigation notice) emitted. Each guard still honours its own
30
30
  // switch (`JOSH_BATCH_GUARD` / `JOSH_INVESTIGATION_GUARD` / `JOSH_RULE_GUARD`) internally, so this
31
31
  // process needs no switch of its own.
32
32
 
33
- // Fold the guards' verdicts into one. A refusal wins over everything else, and the batch guard's
34
- // reason is shown first to match the entry order the separate hooks had. When nothing refuses,
35
- // whatever the batch guard reported (a notice, a fault, or nothing) passes through untouched.
36
- function combine_outcomes(batch: GuardOutcome, extra_reason: string | undefined): GuardOutcome {
37
- if (batch.reason !== undefined) return batch
38
-
39
- if (extra_reason !== undefined) {
40
- return { reason: extra_reason, notice: undefined, fault: undefined }
41
- }
42
-
43
- return batch
44
- }
33
+ const CLEAR: GuardOutcome = { reason: undefined, notice: undefined, fault: undefined }
45
34
 
46
35
  function is_clear(outcome: GuardOutcome): boolean {
47
36
  return outcome.reason === undefined && outcome.notice === undefined && outcome.fault === undefined
48
37
  }
49
38
 
50
- // The duplicate-read guard is `notice`-mode in a lane child, so it is the second guard that can raise a
51
- // non-refusal notice. Its notice is surfaced only where nothing else spoke —
52
- // a refusal from any guard, or the batch guard's own notice, wins first.
53
- function with_duplicate_notice(combined: GuardOutcome, notice: string | undefined): GuardOutcome {
54
- if (notice === undefined || !is_clear(combined)) return combined
39
+ // Fold the guards' verdicts into one. A refusal wins over everything else, in the entry order the
40
+ // separate hooks had: the outcomes in the order given, then the rule guard's reason. When nothing
41
+ // refuses, the first guard that spoke passes its notice or fault through untouched — so a lane child's
42
+ // `notice`-mode guidance reaches the model rather than only spending its stamp (joshuafolkken/kit#3877).
43
+ function combine_outcomes(
44
+ outcomes: ReadonlyArray<GuardOutcome>,
45
+ rule_reason: string | undefined,
46
+ ): GuardOutcome {
47
+ const reason = outcomes.find((outcome) => outcome.reason !== undefined)?.reason ?? rule_reason
55
48
 
56
- return { reason: undefined, notice, fault: undefined }
49
+ if (reason !== undefined) return { reason, notice: undefined, fault: undefined }
50
+
51
+ return outcomes.find((outcome) => !is_clear(outcome)) ?? CLEAR
57
52
  }
58
53
 
59
54
  // The Step 0 reminder is the last word: asked only where every guard stayed
@@ -70,14 +65,13 @@ function with_step_zero_notice(outcome: GuardOutcome, raw_payload: string): Guar
70
65
  // once-per-run stamp, so short-circuiting on the first refusal would change which guard is allowed to
71
66
  // fire on a later call.
72
67
  function guard_outcome(raw_payload: string): GuardOutcome {
73
- const batch = batch_outcome(raw_payload)
74
- const investigation = investigation_refusal(raw_payload)
75
- const duplicate = duplicate_read_outcome(raw_payload)
76
- const rule = delivered_rules.delivery(raw_payload)
77
- const duplicate_refusal = duplicate.reason
78
- const combined = combine_outcomes(batch, investigation ?? duplicate_refusal ?? rule)
68
+ const outcomes = [
69
+ batch_outcome(raw_payload),
70
+ investigation_outcome(raw_payload),
71
+ duplicate_read_outcome(raw_payload),
72
+ ]
79
73
 
80
- return with_duplicate_notice(combined, duplicate.notice)
74
+ return combine_outcomes(outcomes, delivered_rules.delivery(raw_payload))
81
75
  }
82
76
 
83
77
  // The synchronous verdict the Codex adapter asks for, with the Step 0 reminder as its last layer.
@@ -191,7 +185,7 @@ function pipefail_envelope(
191
185
  ): string | undefined {
192
186
  if (input === undefined || outcome.reason !== undefined) return undefined
193
187
 
194
- return hook_decision.rewrite_envelope(
188
+ return hook_decision.flow_rewrite_envelope(
195
189
  input,
196
190
  rewrite_context(piped_verification.PIPEFAIL_NOTE, outcome),
197
191
  )
@@ -65,8 +65,9 @@ function notice(raw_payload: string, root: string = process.cwd()): string | und
65
65
 
66
66
  const target = STAMP.path(transcript)
67
67
 
68
- if (STAMP.last_ms(target) !== hook_decision.NEVER_MS) return undefined
69
- if (!STAMP.record(target, Date.now())) return undefined
68
+ if (STAMP.last_ms(target) !== hook_decision.NEVER_MS || !STAMP.record(target, Date.now())) {
69
+ return undefined
70
+ }
70
71
 
71
72
  return NOTICE
72
73
  }
@@ -65,8 +65,12 @@ function rewrite_command_value(value: string): string {
65
65
  .split(PNPM_JOSH_PREFIX)
66
66
  .join(BUNDLE_INVOCATION)
67
67
 
68
- if (!rewritten.includes(claude_plugin_config.MARKETPLACE_PATH)) return rewritten
69
- if (rewritten.includes(MISSING_INSTALL_GUARD)) return rewritten
68
+ if (
69
+ !rewritten.includes(claude_plugin_config.MARKETPLACE_PATH) ||
70
+ rewritten.includes(MISSING_INSTALL_GUARD)
71
+ ) {
72
+ return rewritten
73
+ }
70
74
 
71
75
  return `${MISSING_INSTALL_GUARD}${rewritten}`
72
76
  }
@@ -30,8 +30,10 @@ function patch_deploy_vps_pnpm(content: string): string {
30
30
  const with_pnpm12 = content.replaceAll(PNPM_INSTALL_PATTERN, (command) =>
31
31
  replace_outdated_install(command),
32
32
  )
33
- if (with_pnpm12.includes(VERSION_CHECK_MARKER)) return with_pnpm12
34
- if (!OLD_PNPM_CHECK_PATTERN.test(with_pnpm12)) return with_pnpm12
33
+
34
+ if (with_pnpm12.includes(VERSION_CHECK_MARKER) || !OLD_PNPM_CHECK_PATTERN.test(with_pnpm12)) {
35
+ return with_pnpm12
36
+ }
35
37
 
36
38
  return with_pnpm12
37
39
  .replace(OLD_MAJOR_LINE_PATTERN, '')
@@ -76,9 +76,12 @@ function firing_point_problem(body: string): string | undefined {
76
76
  }
77
77
 
78
78
  function baseline_problem(body: string): string | undefined {
79
- if (!markdown_section.has_line(body, BASELINE_HEADING)) return undefined
80
-
81
- if (baseline_measure.has_command_value(body)) return undefined
79
+ if (
80
+ !markdown_section.has_line(body, BASELINE_HEADING) ||
81
+ baseline_measure.has_command_value(body)
82
+ ) {
83
+ return undefined
84
+ }
82
85
 
83
86
  return 'baseline must be written as `command → value`, not prose (it has to be re-runnable)'
84
87
  }
@@ -86,9 +89,12 @@ function baseline_problem(body: string): string | undefined {
86
89
  // The reproduction section is checked for format only where the heading is present — a missing heading
87
90
  // is already reported by `heading_problems`, so reporting it here too would double the message.
88
91
  function reproduction_problem(body: string): string | undefined {
89
- if (!markdown_section.has_line(body, REPRODUCTION_HEADING)) return undefined
90
-
91
- if (reproduction_measure.has_command_output(body)) return undefined
92
+ if (
93
+ !markdown_section.has_line(body, REPRODUCTION_HEADING) ||
94
+ reproduction_measure.has_command_output(body)
95
+ ) {
96
+ return undefined
97
+ }
92
98
 
93
99
  return 'reproduction must be a command and its actual output (a fenced block), not prose ("確認した" is not re-runnable)'
94
100
  }
@@ -73,8 +73,9 @@ function read_arguments(argv: ReadonlyArray<string>): ScoutArguments | undefined
73
73
  const { values, positionals } = parsed
74
74
  const [title] = positionals
75
75
 
76
- if (has_invalid_arguments(title, values.body, values['body-file'])) return undefined
77
- if (title === undefined) return undefined
76
+ if (title === undefined || has_invalid_arguments(title, values.body, values['body-file'])) {
77
+ return undefined
78
+ }
78
79
 
79
80
  return { title, body: values.body ?? '', body_file: values['body-file'] }
80
81
  }