git 4.3.2 → 5.0.0.beta.1

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 (280) hide show
  1. checksums.yaml +4 -4
  2. data/.github/copilot-instructions.md +67 -2705
  3. data/.github/pull_request_template.md +3 -1
  4. data/.github/skills/breaking-change-analysis/SKILL.md +102 -0
  5. data/.github/skills/ci-cd-troubleshooting/SKILL.md +264 -0
  6. data/.github/skills/command-implementation/REFERENCE.md +993 -0
  7. data/.github/skills/command-implementation/SKILL.md +229 -0
  8. data/.github/skills/command-test-conventions/SKILL.md +660 -0
  9. data/.github/skills/command-yard-documentation/SKILL.md +426 -0
  10. data/.github/skills/dependency-management/SKILL.md +72 -0
  11. data/.github/skills/development-workflow/SKILL.md +506 -0
  12. data/.github/skills/extract-command-from-lib/SKILL.md +487 -0
  13. data/.github/skills/extract-facade-from-base-lib/SKILL.md +586 -0
  14. data/.github/skills/facade-implementation/REFERENCE.md +840 -0
  15. data/.github/skills/facade-implementation/SKILL.md +260 -0
  16. data/.github/skills/facade-test-conventions/SKILL.md +380 -0
  17. data/.github/skills/facade-yard-documentation/SKILL.md +429 -0
  18. data/.github/skills/make-skill-template/SKILL.md +176 -0
  19. data/.github/skills/pr-readiness-review/SKILL.md +185 -0
  20. data/.github/skills/project-context/SKILL.md +313 -0
  21. data/.github/skills/pull-request-review/SKILL.md +168 -0
  22. data/.github/skills/refactor-command-to-commandlineresult/SKILL.md +131 -0
  23. data/.github/skills/release-management/SKILL.md +125 -0
  24. data/.github/skills/review-arguments-dsl/CHECKLIST.md +788 -0
  25. data/.github/skills/review-arguments-dsl/SKILL.md +214 -0
  26. data/.github/skills/review-backward-compatibility/SKILL.md +275 -0
  27. data/.github/skills/review-cross-command-consistency/SKILL.md +139 -0
  28. data/.github/skills/reviewing-skills/SKILL.md +189 -0
  29. data/.github/skills/rspec-unit-testing-standards/SKILL.md +639 -0
  30. data/.github/skills/tdd-refactor-step/SKILL.md +236 -0
  31. data/.github/skills/test-debugging/SKILL.md +160 -0
  32. data/.github/skills/yard-documentation/SKILL.md +793 -0
  33. data/.github/workflows/continuous_integration.yml +3 -2
  34. data/.github/workflows/enforce_conventional_commits.yml +1 -1
  35. data/.github/workflows/experimental_continuous_integration.yml +2 -2
  36. data/.github/workflows/release.yml +3 -4
  37. data/.gitignore +8 -0
  38. data/.husky/pre-commit +13 -0
  39. data/.release-please-manifest.json +1 -1
  40. data/.rspec +3 -0
  41. data/.rubocop.yml +7 -3
  42. data/.rubocop_todo.yml +23 -5
  43. data/.yardopts +1 -0
  44. data/CHANGELOG.md +0 -40
  45. data/CONTRIBUTING.md +694 -53
  46. data/README.md +17 -5
  47. data/Rakefile +61 -9
  48. data/commitlint.test +4 -0
  49. data/git.gemspec +14 -8
  50. data/lib/git/args_builder.rb +0 -8
  51. data/lib/git/base.rb +486 -410
  52. data/lib/git/branch.rb +380 -43
  53. data/lib/git/branch_delete_failure.rb +31 -0
  54. data/lib/git/branch_delete_result.rb +63 -0
  55. data/lib/git/branch_info.rb +178 -0
  56. data/lib/git/branches.rb +130 -24
  57. data/lib/git/command_line/base.rb +245 -0
  58. data/lib/git/command_line/capturing.rb +249 -0
  59. data/lib/git/command_line/result.rb +96 -0
  60. data/lib/git/command_line/streaming.rb +194 -0
  61. data/lib/git/command_line.rb +43 -322
  62. data/lib/git/command_line_result.rb +4 -88
  63. data/lib/git/commands/add.rb +131 -0
  64. data/lib/git/commands/am/abort.rb +43 -0
  65. data/lib/git/commands/am/apply.rb +252 -0
  66. data/lib/git/commands/am/continue.rb +43 -0
  67. data/lib/git/commands/am/quit.rb +43 -0
  68. data/lib/git/commands/am/retry.rb +47 -0
  69. data/lib/git/commands/am/show_current_patch.rb +64 -0
  70. data/lib/git/commands/am/skip.rb +42 -0
  71. data/lib/git/commands/am.rb +33 -0
  72. data/lib/git/commands/apply.rb +237 -0
  73. data/lib/git/commands/archive/list_formats.rb +46 -0
  74. data/lib/git/commands/archive.rb +140 -0
  75. data/lib/git/commands/arguments.rb +3510 -0
  76. data/lib/git/commands/base.rb +403 -0
  77. data/lib/git/commands/branch/copy.rb +94 -0
  78. data/lib/git/commands/branch/create.rb +173 -0
  79. data/lib/git/commands/branch/delete.rb +80 -0
  80. data/lib/git/commands/branch/list.rb +162 -0
  81. data/lib/git/commands/branch/move.rb +94 -0
  82. data/lib/git/commands/branch/set_upstream.rb +86 -0
  83. data/lib/git/commands/branch/show_current.rb +49 -0
  84. data/lib/git/commands/branch/unset_upstream.rb +57 -0
  85. data/lib/git/commands/branch.rb +34 -0
  86. data/lib/git/commands/cat_file/batch.rb +364 -0
  87. data/lib/git/commands/cat_file/filtered.rb +105 -0
  88. data/lib/git/commands/cat_file/raw.rb +210 -0
  89. data/lib/git/commands/cat_file.rb +49 -0
  90. data/lib/git/commands/checkout/branch.rb +151 -0
  91. data/lib/git/commands/checkout/files.rb +115 -0
  92. data/lib/git/commands/checkout.rb +38 -0
  93. data/lib/git/commands/checkout_index.rb +105 -0
  94. data/lib/git/commands/clean.rb +100 -0
  95. data/lib/git/commands/clone.rb +240 -0
  96. data/lib/git/commands/commit.rb +272 -0
  97. data/lib/git/commands/commit_tree.rb +100 -0
  98. data/lib/git/commands/config_option_syntax/add.rb +83 -0
  99. data/lib/git/commands/config_option_syntax/get.rb +117 -0
  100. data/lib/git/commands/config_option_syntax/get_all.rb +115 -0
  101. data/lib/git/commands/config_option_syntax/get_color.rb +91 -0
  102. data/lib/git/commands/config_option_syntax/get_color_bool.rb +93 -0
  103. data/lib/git/commands/config_option_syntax/get_regexp.rb +115 -0
  104. data/lib/git/commands/config_option_syntax/get_urlmatch.rb +102 -0
  105. data/lib/git/commands/config_option_syntax/list.rb +107 -0
  106. data/lib/git/commands/config_option_syntax/remove_section.rb +74 -0
  107. data/lib/git/commands/config_option_syntax/rename_section.rb +78 -0
  108. data/lib/git/commands/config_option_syntax/replace_all.rb +104 -0
  109. data/lib/git/commands/config_option_syntax/set.rb +114 -0
  110. data/lib/git/commands/config_option_syntax/unset.rb +89 -0
  111. data/lib/git/commands/config_option_syntax/unset_all.rb +89 -0
  112. data/lib/git/commands/config_option_syntax.rb +56 -0
  113. data/lib/git/commands/describe.rb +155 -0
  114. data/lib/git/commands/diff.rb +656 -0
  115. data/lib/git/commands/diff_files.rb +518 -0
  116. data/lib/git/commands/diff_index.rb +496 -0
  117. data/lib/git/commands/fetch.rb +352 -0
  118. data/lib/git/commands/fsck.rb +136 -0
  119. data/lib/git/commands/gc.rb +132 -0
  120. data/lib/git/commands/grep.rb +338 -0
  121. data/lib/git/commands/init.rb +99 -0
  122. data/lib/git/commands/log.rb +632 -0
  123. data/lib/git/commands/ls_files.rb +191 -0
  124. data/lib/git/commands/ls_remote.rb +155 -0
  125. data/lib/git/commands/ls_tree.rb +131 -0
  126. data/lib/git/commands/maintenance/register.rb +75 -0
  127. data/lib/git/commands/maintenance/run.rb +104 -0
  128. data/lib/git/commands/maintenance/start.rb +66 -0
  129. data/lib/git/commands/maintenance/stop.rb +55 -0
  130. data/lib/git/commands/maintenance/unregister.rb +79 -0
  131. data/lib/git/commands/maintenance.rb +31 -0
  132. data/lib/git/commands/merge/abort.rb +44 -0
  133. data/lib/git/commands/merge/continue.rb +44 -0
  134. data/lib/git/commands/merge/quit.rb +46 -0
  135. data/lib/git/commands/merge/start.rb +245 -0
  136. data/lib/git/commands/merge.rb +28 -0
  137. data/lib/git/commands/merge_base.rb +86 -0
  138. data/lib/git/commands/mv.rb +77 -0
  139. data/lib/git/commands/name_rev.rb +114 -0
  140. data/lib/git/commands/pull.rb +377 -0
  141. data/lib/git/commands/push.rb +246 -0
  142. data/lib/git/commands/read_tree.rb +149 -0
  143. data/lib/git/commands/remote/add.rb +91 -0
  144. data/lib/git/commands/remote/get_url.rb +66 -0
  145. data/lib/git/commands/remote/list.rb +54 -0
  146. data/lib/git/commands/remote/prune.rb +61 -0
  147. data/lib/git/commands/remote/remove.rb +52 -0
  148. data/lib/git/commands/remote/rename.rb +69 -0
  149. data/lib/git/commands/remote/set_branches.rb +63 -0
  150. data/lib/git/commands/remote/set_head.rb +82 -0
  151. data/lib/git/commands/remote/set_url.rb +71 -0
  152. data/lib/git/commands/remote/set_url_add.rb +61 -0
  153. data/lib/git/commands/remote/set_url_delete.rb +64 -0
  154. data/lib/git/commands/remote/show.rb +71 -0
  155. data/lib/git/commands/remote/update.rb +72 -0
  156. data/lib/git/commands/remote.rb +42 -0
  157. data/lib/git/commands/repack.rb +277 -0
  158. data/lib/git/commands/reset.rb +147 -0
  159. data/lib/git/commands/rev_parse.rb +297 -0
  160. data/lib/git/commands/revert/abort.rb +45 -0
  161. data/lib/git/commands/revert/continue.rb +57 -0
  162. data/lib/git/commands/revert/quit.rb +47 -0
  163. data/lib/git/commands/revert/skip.rb +44 -0
  164. data/lib/git/commands/revert/start.rb +153 -0
  165. data/lib/git/commands/revert.rb +29 -0
  166. data/lib/git/commands/rm.rb +114 -0
  167. data/lib/git/commands/show.rb +632 -0
  168. data/lib/git/commands/show_ref/exclude_existing.rb +120 -0
  169. data/lib/git/commands/show_ref/exists.rb +78 -0
  170. data/lib/git/commands/show_ref/list.rb +145 -0
  171. data/lib/git/commands/show_ref/verify.rb +120 -0
  172. data/lib/git/commands/show_ref.rb +42 -0
  173. data/lib/git/commands/stash/apply.rb +75 -0
  174. data/lib/git/commands/stash/branch.rb +65 -0
  175. data/lib/git/commands/stash/clear.rb +41 -0
  176. data/lib/git/commands/stash/create.rb +58 -0
  177. data/lib/git/commands/stash/drop.rb +67 -0
  178. data/lib/git/commands/stash/list.rb +39 -0
  179. data/lib/git/commands/stash/pop.rb +78 -0
  180. data/lib/git/commands/stash/push.rb +103 -0
  181. data/lib/git/commands/stash/show.rb +149 -0
  182. data/lib/git/commands/stash/store.rb +63 -0
  183. data/lib/git/commands/stash.rb +38 -0
  184. data/lib/git/commands/status.rb +169 -0
  185. data/lib/git/commands/symbolic_ref/delete.rb +68 -0
  186. data/lib/git/commands/symbolic_ref/read.rb +95 -0
  187. data/lib/git/commands/symbolic_ref/update.rb +76 -0
  188. data/lib/git/commands/symbolic_ref.rb +38 -0
  189. data/lib/git/commands/tag/create.rb +139 -0
  190. data/lib/git/commands/tag/delete.rb +55 -0
  191. data/lib/git/commands/tag/list.rb +143 -0
  192. data/lib/git/commands/tag/verify.rb +71 -0
  193. data/lib/git/commands/tag.rb +26 -0
  194. data/lib/git/commands/update_ref/batch.rb +140 -0
  195. data/lib/git/commands/update_ref/delete.rb +92 -0
  196. data/lib/git/commands/update_ref/update.rb +106 -0
  197. data/lib/git/commands/update_ref.rb +42 -0
  198. data/lib/git/commands/version.rb +52 -0
  199. data/lib/git/commands/worktree/add.rb +140 -0
  200. data/lib/git/commands/worktree/list.rb +64 -0
  201. data/lib/git/commands/worktree/lock.rb +58 -0
  202. data/lib/git/commands/worktree/management_base.rb +51 -0
  203. data/lib/git/commands/worktree/move.rb +66 -0
  204. data/lib/git/commands/worktree/prune.rb +67 -0
  205. data/lib/git/commands/worktree/remove.rb +63 -0
  206. data/lib/git/commands/worktree/repair.rb +76 -0
  207. data/lib/git/commands/worktree/unlock.rb +47 -0
  208. data/lib/git/commands/worktree.rb +43 -0
  209. data/lib/git/commands/write_tree.rb +68 -0
  210. data/lib/git/commands.rb +89 -0
  211. data/lib/git/detached_head_info.rb +54 -0
  212. data/lib/git/diff.rb +297 -7
  213. data/lib/git/diff_file_numstat_info.rb +29 -0
  214. data/lib/git/diff_file_patch_info.rb +134 -0
  215. data/lib/git/diff_file_raw_info.rb +127 -0
  216. data/lib/git/diff_info.rb +169 -0
  217. data/lib/git/diff_path_status.rb +78 -19
  218. data/lib/git/diff_result.rb +32 -0
  219. data/lib/git/diff_stats.rb +59 -14
  220. data/lib/git/dirstat_info.rb +86 -0
  221. data/lib/git/errors.rb +65 -2
  222. data/lib/git/execution_context/global.rb +56 -0
  223. data/lib/git/execution_context/repository.rb +147 -0
  224. data/lib/git/execution_context.rb +482 -0
  225. data/lib/git/file_ref.rb +74 -0
  226. data/lib/git/fsck_object.rb +9 -9
  227. data/lib/git/fsck_result.rb +1 -1
  228. data/lib/git/lib.rb +1606 -1028
  229. data/lib/git/log.rb +15 -2
  230. data/lib/git/object.rb +92 -22
  231. data/lib/git/parsers/branch.rb +224 -0
  232. data/lib/git/parsers/cat_file.rb +111 -0
  233. data/lib/git/parsers/diff.rb +585 -0
  234. data/lib/git/parsers/fsck.rb +133 -0
  235. data/lib/git/parsers/grep.rb +42 -0
  236. data/lib/git/parsers/ls_tree.rb +58 -0
  237. data/lib/git/parsers/stash.rb +208 -0
  238. data/lib/git/parsers/tag.rb +257 -0
  239. data/lib/git/remote.rb +133 -9
  240. data/lib/git/repository/branching.rb +572 -0
  241. data/lib/git/repository/committing.rb +191 -0
  242. data/lib/git/repository/configuring.rb +156 -0
  243. data/lib/git/repository/diffing.rb +775 -0
  244. data/lib/git/repository/inspecting.rb +153 -0
  245. data/lib/git/repository/logging.rb +247 -0
  246. data/lib/git/repository/merging.rb +295 -0
  247. data/lib/git/repository/object_operations.rb +1101 -0
  248. data/lib/git/repository/path_resolver.rb +207 -0
  249. data/lib/git/repository/remote_operations.rb +753 -0
  250. data/lib/git/repository/shared_private.rb +51 -0
  251. data/lib/git/repository/staging.rb +390 -0
  252. data/lib/git/repository/stashing.rb +107 -0
  253. data/lib/git/repository/status_operations.rb +180 -0
  254. data/lib/git/repository/worktree_operations.rb +159 -0
  255. data/lib/git/repository.rb +264 -1
  256. data/lib/git/stash.rb +85 -4
  257. data/lib/git/stash_info.rb +104 -0
  258. data/lib/git/stashes.rb +130 -13
  259. data/lib/git/status.rb +224 -18
  260. data/lib/git/tag_delete_failure.rb +31 -0
  261. data/lib/git/tag_delete_result.rb +63 -0
  262. data/lib/git/tag_info.rb +105 -0
  263. data/lib/git/version.rb +109 -2
  264. data/lib/git/version_constraint.rb +81 -0
  265. data/lib/git/worktree.rb +120 -5
  266. data/lib/git/worktrees.rb +107 -7
  267. data/lib/git.rb +114 -18
  268. data/redesign/1_architecture_existing.md +54 -18
  269. data/redesign/2_architecture_redesign.md +365 -46
  270. data/redesign/3_architecture_implementation.md +1451 -54
  271. data/tasks/gem_tasks.rake +4 -0
  272. data/tasks/npm_tasks.rake +7 -0
  273. data/tasks/rspec.rake +48 -0
  274. data/tasks/test.rake +13 -1
  275. data/tasks/yard.rake +34 -7
  276. metadata +349 -20
  277. data/lib/git/index.rb +0 -6
  278. data/lib/git/path.rb +0 -38
  279. data/lib/git/working_directory.rb +0 -6
  280. /data/{release-please-config.json → .release-please-config.json} +0 -0
@@ -0,0 +1,788 @@
1
+ # Arguments DSL Checklist
2
+
3
+ - [1. Determine scope and exclusions](#1-determine-scope-and-exclusions)
4
+ - [Options excluded because they belong to a different sub-action](#options-excluded-because-they-belong-to-a-different-sub-action)
5
+ - [Options excluded due to execution-model conflicts](#options-excluded-due-to-execution-model-conflicts)
6
+ - [2. Verify DSL method per option type](#2-verify-dsl-method-per-option-type)
7
+ - [Recognizing `flag_or_value_option` from the git docs](#recognizing-flag_or_value_option-from-the-git-docs)
8
+ - [Action-option-with-optional-value commands](#action-option-with-optional-value-commands)
9
+ - [Choosing the correct pathspec form](#choosing-the-correct-pathspec-form)
10
+ - [Quick reference](#quick-reference)
11
+ - [3. Verify alias and `as:` usage](#3-verify-alias-and-as-usage)
12
+ - [The `as:` escape hatch](#the-as-escape-hatch)
13
+ - [Prefer first-class DSL features over `as:`](#prefer-first-class-dsl-features-over-as)
14
+ - [Single-char flags never need `as:`](#single-char-flags-never-need-as)
15
+ - [Short-flag alias completeness](#short-flag-alias-completeness)
16
+ - [Spurious aliases](#spurious-aliases)
17
+ - [4. Verify ordering](#4-verify-ordering)
18
+ - [Comments in the DSL block](#comments-in-the-dsl-block)
19
+ - [`end_of_options` placement](#end_of_options-placement)
20
+ - [Rule 1 — SYNOPSIS shows `--`: mirror the SYNOPSIS](#rule-1--synopsis-shows----mirror-the-synopsis)
21
+ - [Rule 2 — SYNOPSIS does NOT show `--`: protect operands from flag misinterpretation](#rule-2--synopsis-does-not-show----protect-operands-from-flag-misinterpretation)
22
+ - [Choosing the `as:` token](#choosing-the-as-token)
23
+ - [5. Verify modifiers](#5-verify-modifiers)
24
+ - [`execution_option` usage](#execution_option-usage)
25
+ - [6. Check completeness](#6-check-completeness)
26
+ - [YARD documentation ↔ DSL parity](#yard-documentation--dsl-parity)
27
+ - [Repeatable boolean flags](#repeatable-boolean-flags)
28
+ - [Operand naming](#operand-naming)
29
+ - [Per-argument validation completeness](#per-argument-validation-completeness)
30
+ - [7. Check class-level declarations](#7-check-class-level-declarations)
31
+
32
+ ## 1. Determine scope and exclusions
33
+
34
+ Before auditing individual DSL entries, determine which git options are in scope.
35
+ Reference documents and source files are loaded during the [Input phase](SKILL.md#input).
36
+
37
+ ### Options excluded because they belong to a different sub-action
38
+
39
+ When a command is split into sub-command classes (e.g., `Branch::Create` vs.
40
+ `Branch::List`), each class includes **only** the options that apply to its
41
+ sub-action. Do **not** add every option from the man page — git documents all modes
42
+ on a single page.
43
+
44
+ To determine which options belong to a sub-action:
45
+
46
+ 1. **Read the SYNOPSIS** — git man pages list separate SYNOPSIS lines per mode.
47
+ Only options shown on the SYNOPSIS line for the target sub-action are candidates.
48
+ 2. **Cross-reference DESCRIPTION and OPTIONS sections** — check each option's
49
+ description for phrases like "only useful with `--list`" or "when used with
50
+ `-d`". If the docs explicitly tie an option to a different mode, exclude it.
51
+ 3. **Common/shared options** — options on every SYNOPSIS line or described as
52
+ applying to the command as a whole (e.g., `--quiet`, `--verbose`) belong in
53
+ every sub-command class where they are meaningful.
54
+
55
+ This rule applies **only** to split commands. For single-class commands, include all
56
+ options (subject to execution-model exclusions below).
57
+
58
+ ### Options excluded due to execution-model conflicts
59
+
60
+ Include ALL git options in the DSL by default — including output-format flags such as
61
+ `--patch`, `--numstat`, `--raw`, `--format=…`, `--pretty=…`, `--no-color`, etc.
62
+
63
+ The only options that should be **excluded** are those that conflict with the
64
+ subprocess execution model: options that require TTY input or otherwise make the
65
+ command incompatible with non-interactive subprocess execution:
66
+
67
+ Examples of options to **exclude** (execution-model conflicts):
68
+
69
+ - `--interactive` / `-i` — opens an interactive menu; requires a TTY
70
+ - `--patch` (interactive form, e.g. `git add -p`) — requires TTY prompts
71
+ - Any option whose git implementation requires stdin/TTY interaction the library
72
+ cannot provide
73
+
74
+ Examples of options to **include** (no execution-model conflict):
75
+
76
+ - `--format=<fmt>`, `--pretty=<fmt>`, `--porcelain` — output format flags; the facade
77
+ passes these explicitly when the parser requires a specific format
78
+ - `--patch` (diff output mode, e.g. `git diff --patch`), `--numstat`, `--shortstat`,
79
+ `--raw` — output mode flags used by the facade to select a parseable format
80
+
81
+ > **Note on `--patch`:** it appears in both lists because the flag has two different
82
+ > git behaviors depending on the command. In `git add -p` it opens an interactive
83
+ > session (exclude). In `git diff --patch` it selects a non-interactive output format
84
+ > (include). Evaluate per-command, not globally.
85
+
86
+ - `--no-color` — facade passes this to prevent ANSI escape codes from breaking
87
+ parsing
88
+ - `--verbose` / `-v`, `--quiet` / `-q` — include these unless they open a TTY
89
+
90
+ **Default assumption for `--verbose` and `--quiet`:** declare as `flag_option`
91
+ (not `literal`) unless their git implementation requires interactive I/O.
92
+
93
+ Command classes are neutral — they never hardcode `literal` entries for
94
+ output-control, editor-suppression, or progress flags. Declare these as
95
+ `flag_option` / `value_option` so the facade can pass the policy value.
96
+
97
+ > **Anti-pattern:** `literal '--no-edit'`, `literal '--verbose'`,
98
+ > `literal '--no-progress'` inside a command class.
99
+ >
100
+ > **Correct pattern:** `flag_option :edit, negatable: true` in the command;
101
+ > `no_edit: true` passed from the facade call site.
102
+
103
+ **The `--edit` / `--no-edit` pair:** Model as `flag_option :edit, negatable: true`.
104
+ The facade (`Git::Lib`) passes `no_edit: true` at each call site. Do **not** hardcode
105
+ `literal '--no-edit'` — that prevents the facade from controlling the option — and do
106
+ **not** exclude `--edit` from the DSL.
107
+
108
+ **Output-format options belong at the facade call site, not as `literal` entries:**
109
+ When a parser requires specific output flags (e.g. `--pretty=raw`, `--numstat`),
110
+ declare those flags in the DSL with `flag_option` or `value_option`, and pass them
111
+ explicitly from `Git::Lib`. Never hardcode them as `literal` entries inside the
112
+ command class — that hides the parser contract and prevents the facade from choosing
113
+ the format. See Insight 16 in `redesign/3_architecture_implementation.md`.
114
+
115
+ ## 2. Verify DSL method per option type
116
+
117
+ | Git behavior | DSL method | Example |
118
+ | --- | --- | --- |
119
+ | fixed flag always present | `literal` | `literal 'stash'` — **only** for operation selectors (subcommand names, mode flags like `--delete` that define what the class does) |
120
+ | boolean flag | `flag_option` | `flag_option :cached` |
121
+ | repeatable boolean flag | `flag_option ..., max_times: N` | `flag_option %i[force f], max_times: 2` |
122
+ | boolean-or-value | `flag_or_value_option` | `flag_or_value_option :dirstat, inline: true` |
123
+ | value option | `value_option` | `value_option :message` |
124
+ | key-value pair option (inherently repeatable via Hash/Array input) | `key_value_option` | `key_value_option :trailer, key_separator: ': '` — caller passes `trailer: { 'Signed-off-by' => 'Name' }` or `trailer: [['key', 'val']]` |
125
+ | option requiring custom builder logic | `custom_option` | `custom_option :pattern, required: true do |val| ... end` — builder block receives the value and returns CLI args |
126
+ | execution kwarg (not a CLI arg) | `execution_option` | `execution_option :timeout` |
127
+ | positional argument | `operand` | `operand :commit1` |
128
+ | pathspec-style operands (independently reachable after `--`) | `end_of_options` + `value_option ... as_operand: true` | `end_of_options; value_option :pathspec, as_operand: true, repeatable: true` — caller passes `pathspec: ['f1', 'f2']` |
129
+ | pathspec-style operands (only positional group, no earlier positional ambiguity) | `operand ...` | `operand :pathspec, repeatable: true` — caller passes positionals `cmd.call('f1', 'f2')` |
130
+
131
+ ### Recognizing `flag_or_value_option` from the git docs
132
+
133
+ git command documentation uses **`[=<value>]`** (square-bracketed
134
+ `=<value>`) to mark an option's value as optional. That notation maps directly
135
+ to `flag_or_value_option`:
136
+
137
+ | Man-page signature | DSL method |
138
+ | --- | --- |
139
+ | `--foo` | `flag_option :foo` |
140
+ | `--foo` / `--no-foo` | `flag_option :foo, negatable: true` |
141
+ | `--foo=<value>` | `value_option :foo, inline: true` |
142
+ | `--foo[=<value>]` | `flag_or_value_option :foo, inline: true` |
143
+ | `--foo[=<value>]` / `--no-foo` | `flag_or_value_option :foo, negatable: true, inline: true` |
144
+ | `--foo <value>` | `value_option :foo` |
145
+
146
+ **Why `inline: true` appears in every `=` row:** The `=` in man-page notation
147
+ (`--foo=<value>`, `--foo[=<value>]`) means git expects the value joined to the
148
+ flag as a single argv token (`--foo=bar`). The `inline: true` modifier tells the
149
+ DSL builder to emit that joined form. Without it, the value is emitted as a
150
+ **separate** argv token (`--foo bar`), which is the correct behavior when the git
151
+ docs show a space between the flag and value (`--foo <value>`). Match the
152
+ man-page notation: `=` → `inline: true`; space → omit `inline:`.
153
+
154
+ Common examples: `--branches[=<pattern>]`, `--tags[=<pattern>]`,
155
+ `--remotes[=<pattern>]`, `--dirstat[=<param>...]`.
156
+
157
+ Negatable value-option examples: `--track[=direct|inherit]` / `--no-track`,
158
+ `--recurse-submodules[=yes|on-demand|no]` / `--no-recurse-submodules`.
159
+
160
+ **Do not** use `flag_option` for these — it silently drops the value when one is
161
+ supplied.
162
+
163
+ ### Action-option-with-optional-value commands
164
+
165
+ Some git commands express their **primary action** as an option with an optional
166
+ value (man-page notation: `--flag[=<value>]`). The canonical example is
167
+ `git am --show-current-patch[=(diff|raw)]` — there is no mode where the flag is
168
+ *not* passed; the optional `=<value>` just refines the behavior.
169
+
170
+ This situation is distinct from a normal `flag_or_value_option` used as a
171
+ modifier: the option IS the command. Do **not** model this as `literal
172
+ '--show-current-patch'` — that precludes passing the optional value.
173
+
174
+ **DSL entry:**
175
+
176
+ ```ruby
177
+ flag_or_value_option :show_current_patch, inline: true, type: [TrueClass, String]
178
+ ```
179
+
180
+ **Required `#call` override** — the class must provide a positional `#call`
181
+ override that maps the positional API onto the option keyword:
182
+
183
+ ```ruby
184
+ def call(value = true, *, **)
185
+ super(*, **, show_current_patch: value)
186
+ end
187
+ ```
188
+
189
+ Where:
190
+
191
+ - `value = true` — `true` emits `--flag` alone; a String emits `--flag=value`
192
+ - `*` — forwards any positional operands declared in the DSL (omit when none)
193
+ - `**` — forwards keyword options; unknown keywords raise `ArgumentError`
194
+ - `show_current_patch: value` — uses the actual option keyword name; placed last
195
+ so the positional arg always wins
196
+
197
+ **Flag these as errors:**
198
+
199
+ - Using `literal '--flag'` when the man page shows `--flag[=<value>]`
200
+ - Omitting `type: [TrueClass, String]` — allows `false` to silently pass, which
201
+ emits nothing and produces no error
202
+ - Omitting the `#call` override — forces callers to use the awkward keyword form
203
+ `.call(option_name: true)` instead of the natural `.call` or `.call('diff')`
204
+ - Using `nil` as the default in the override instead of `true` (use
205
+ `value = true`, not `value = nil` with `|| true`)
206
+
207
+ ### Choosing the correct pathspec form
208
+
209
+ Choose the pathspec form by answering one question from the git command doc
210
+ SYNOPSIS: **can the pathspec group be supplied independently of earlier positional
211
+ operands?**
212
+
213
+ If **yes**, use `end_of_options` plus `value_option … as_operand: true` so the
214
+ caller can supply the pathspec group without accidentally binding it to an earlier
215
+ operand.
216
+
217
+ If **no**, use plain `operand` entries so left-to-right positional binding mirrors
218
+ the SYNOPSIS.
219
+
220
+ **Independently reachable pathspec group → `value_option … as_operand: true`**
221
+
222
+ When git explicitly separates two optional groups with `--` (e.g., `git diff
223
+ [<tree-ish>] [--] [<pathspec>...]`), the post-`--` group is *independently reachable*
224
+ — a caller must be able to supply pathspecs without also providing a tree-ish. Use
225
+ the `value_option … as_operand: true` form so positional binding is unambiguous:
226
+
227
+ ```ruby
228
+ operand :tree_ish # positional
229
+ end_of_options # options/operands boundary
230
+ value_option :pathspec, as_operand: true, repeatable: true # as_operand
231
+
232
+ # cmd.call → git diff
233
+ # cmd.call('HEAD~3') → git diff HEAD~3
234
+ # cmd.call(pathspec: ['file.rb']) → git diff -- file.rb
235
+ # cmd.call('HEAD~3', pathspec: ['f.rb']) → git diff HEAD~3 -- f.rb
236
+ ```
237
+
238
+ Without the `value_option … as_operand: true` form, `cmd.call('file.rb')` would silently bind `'file.rb'` to
239
+ `:tree_ish` instead of treating it as a pathspec.
240
+
241
+ The same rule applies when the SYNOPSIS has only a post-`--` pathspec group and no
242
+ earlier operands:
243
+
244
+ ```ruby
245
+ end_of_options
246
+ value_option :pathspec, as_operand: true, repeatable: true, required: true
247
+
248
+ # cmd.call('file.rb') → git <cmd> -- file.rb
249
+ # cmd.call('f1', 'f2') → git <cmd> -- f1 f2
250
+ ```
251
+
252
+ **Pure positional nesting → plain `operand` entries**
253
+
254
+ When the SYNOPSIS shows nested brackets — `[<commit1> [<commit2>]]` — the second
255
+ operand is only meaningful when the first is also present. No caller would ever
256
+ supply `commit2` without `commit1`. Left-to-right binding is correct:
257
+
258
+ ```ruby
259
+ operand :commit1 # optional
260
+ operand :commit2 # optional — only meaningful when commit1 is also given
261
+
262
+ # cmd.call → git diff
263
+ # cmd.call('HEAD~3') → git diff HEAD~3
264
+ # cmd.call('HEAD~3', 'HEAD') → git diff HEAD~3 HEAD
265
+ ```
266
+
267
+ ### Quick reference
268
+
269
+ | git SYNOPSIS shape | Meaning | DSL shape |
270
+ | --- | --- | --- |
271
+ | `[<a>] [--] [<b>...]` | `<b>` is independently reachable | `operand :a` + `end_of_options` + `value_option :b, as_operand: true, repeatable: true` |
272
+ | `[--] <pathspec>...` | required pathspec group after `--` | `end_of_options` + `value_option :pathspec, as_operand: true, repeatable: true, required: true` |
273
+ | `[--] [<pathspec>...]` | optional pathspec group after `--` | `end_of_options` + `value_option :pathspec, as_operand: true, repeatable: true` |
274
+ | `<pathspec>...` | only positional group; no earlier positional ambiguity | `operand :pathspec, repeatable: true, required: true` |
275
+ | `[<a> [<b>]]` | pure left-to-right nesting | `operand :a` + `operand :b` |
276
+
277
+ Use `value_option … as_operand: true` whenever the post-`--` group must be
278
+ addressable without binding through earlier positional operands. Use plain
279
+ `operand` entries only when left-to-right positional binding is unambiguous and
280
+ matches the SYNOPSIS.
281
+
282
+ ## 3. Verify alias and `as:` usage
283
+
284
+ - Prefer aliases for long/short pairs (`%i[force f]`, `%i[all A]`, `%i[intent_to_add
285
+ N]`)
286
+ - Ensure long name is first in alias arrays
287
+
288
+ - **Do not** flag uppercase short-flag aliases (e.g. `:A`, `:N`) as needing `as:` —
289
+ the DSL preserves symbol case, so `:A` correctly produces `-A` without any override
290
+
291
+ ### The `as:` escape hatch
292
+
293
+ `as:` bypasses the DSL's automatic name-to-flag mapping and emits its value verbatim.
294
+ This is intentional power — but it carries a cost: a reviewer can no longer verify
295
+ the flag by reading the symbol name alone. The `as:` string must be audited
296
+ separately, making it harder to spot typos and drift.
297
+
298
+ Flag any use of `as:` unless one of these conditions applies:
299
+
300
+ 1. **Ruby keyword conflict** — the git flag's natural name is a Ruby keyword and
301
+ cannot be used as a symbol literal. The alias is renamed, and `as:` supplies the
302
+ real flag:
303
+
304
+ ```ruby
305
+ flag_option %i[begin_rev], as: '--begin'
306
+ ```
307
+
308
+ 2. **The required argv cannot be expressed by the normal DSL mapping** — the
309
+ symbol name, aliases, and existing modifiers (`negatable:`, `inline:`,
310
+ `as_operand:`, `max_times:`, etc.) cannot produce the required token sequence,
311
+ so `as:` is the narrowest accurate escape hatch. Example:
312
+
313
+ ```ruby
314
+ # :three_way auto-maps to --three-way, but git expects --3way
315
+ flag_option :three_way, as: '--3way'
316
+ ```
317
+
318
+ Outside these cases, `as:` is a red flag. A DSL entry that uses `as:` where a
319
+ plain symbol, alias, or existing modifier would suffice should be corrected.
320
+
321
+ #### Prefer first-class DSL features over `as:`
322
+
323
+ When the DSL now has a first-class way to express the behavior, `as:` is no longer
324
+ justified. Repeated flags are the canonical example: use `max_times:` instead of
325
+ encoding repetition manually.
326
+
327
+ The following patterns should be flagged as errors because `max_times:` expresses
328
+ them directly:
329
+
330
+ - **Combined short flag used to emulate repetition** (e.g. `flag_option %i[force_force ff], as: '-ff'`) —
331
+ replace with `flag_option %i[force f], max_times: 2`
332
+ - **Repeated identical tokens encoded as an array** (e.g. `flag_option :double_force, as: ['--force', '--force']`) —
333
+ replace with `flag_option %i[force f], max_times: 2`
334
+
335
+ #### Single-char flags never need `as:`
336
+
337
+ When git documents a flag as a bare short flag (e.g. `-p`, `-v`, `-q`), name the
338
+ symbol after the flag character directly — do **not** invent a descriptive name and
339
+ compensate with `as:`:
340
+
341
+ ```ruby
342
+ # ❌ Wrong — descriptive name masking the real flag
343
+ flag_option :pretty, as: '-p'
344
+ flag_option :verbose, as: '-v'
345
+
346
+ # ✅ Correct — symbol IS the flag; no as: needed
347
+ flag_option :p
348
+ flag_option :v
349
+ ```
350
+
351
+ The caller passes `p: true` or `v: true`. The symbol name is the single source of
352
+ truth and can be verified at a glance without auditing the `as:` string.
353
+
354
+ ### Short-flag alias completeness
355
+
356
+ Every option that the git documentation documents with a short form must have an
357
+ alias with the long name first:
358
+
359
+ ```ruby
360
+ flag_option %i[regexp_ignore_case i]
361
+ flag_option %i[extended_regexp E]
362
+ flag_option %i[fixed_strings F]
363
+ ```
364
+
365
+ When reviewing, scan the git command doc's option headings for lines of the form
366
+ `-X` / `--long-name` and verify each has a corresponding `%i[long_name X]` alias in
367
+ the DSL. Missing short aliases are a completeness defect, not just a convenience
368
+ omission — callers who pass the short key `:E` will get an `ArgumentError` rather
369
+ than the expected flag.
370
+
371
+ ### Spurious aliases
372
+
373
+ **Never invent an alias that the git command document does not document.** Check the
374
+ git command document before adding any alias entry. The canonical audit is: does the
375
+ git command document show the alias on the same option heading as the primary name?
376
+ If not, do not add it to the alias list.
377
+
378
+ Flag any alias that cannot be found in the git command document's option headings as
379
+ an error (not just a style issue).
380
+
381
+ ## 4. Verify ordering
382
+
383
+ literal options should always come first.
384
+
385
+ For other options mirror the order those options appear in the git command document's
386
+ SYNOPSIS section for the subcommand being implemented.
387
+
388
+ Options that appear only in the OPTIONS section (not the SYNOPSIS) go after all
389
+ SYNOPSIS-ordered options but before `execution_option` declarations. Among
390
+ themselves, mirror the order found in the OPTIONS section.
391
+
392
+ `execution_option` declarations go after all CLI-producing options (`flag_option`,
393
+ `value_option`, `flag_or_value_option`, `key_value_option`, `custom_option`) and
394
+ before `end_of_options` and `operand` declarations. Since `execution_option` never
395
+ emits CLI arguments, its position does not affect the generated command line, but
396
+ consistent placement keeps the DSL block readable.
397
+
398
+ operands should go last.
399
+
400
+ ### Comments in the DSL block
401
+
402
+ **Section comments** (e.g. `# Output format`, `# Whitespace handling`) are encouraged
403
+ to group related options within large DSL blocks. Place them on the line immediately
404
+ before the first option in the group.
405
+
406
+ **NEVER add trailing inline comments to DSL entries.** This is a hard rule — not a
407
+ style preference. Comments like `flag_option :verbose # --verbose` or
408
+ `flag_option :full, negatable: true # --[no-]full` are **always wrong** in this
409
+ project. They were removed project-wide in commit 370dffb because they:
410
+
411
+ 1. Duplicate information the DSL already encodes deterministically
412
+ 2. Duplicate the YARD `@option` documentation
413
+ 3. Create a false verification layer (reviewer checks comment ↔ DSL without
414
+ catching errors in either)
415
+ 4. Create line-length pressure and alignment maintenance burden
416
+
417
+ Flag any trailing inline comment on a DSL entry as an error. This applies when
418
+ scaffolding new commands, updating existing commands, and reviewing commands.
419
+
420
+ ### `end_of_options` placement
421
+
422
+ Determine placement based on whether the SYNOPSIS explicitly shows `--`:
423
+
424
+ #### Rule 1 — SYNOPSIS shows `--`: mirror the SYNOPSIS
425
+
426
+ When the SYNOPSIS explicitly shows `--`, place `end_of_options` in
427
+ the same position the SYNOPSIS shows it. See [Choosing the correct pathspec
428
+ form](#choosing-the-correct-pathspec-form) for how to model the operands that come
429
+ after `--`.
430
+
431
+ **Do not apply Rule 2** when Rule 1 applies.
432
+
433
+ ```ruby
434
+ # git diff [<tree-ish>] [--] [<pathspec>...]
435
+ operand :tree_ish # BEFORE end_of_options
436
+ end_of_options # mirrors SYNOPSIS position
437
+ value_option :pathspec, as_operand: true, repeatable: true # AFTER end_of_options
438
+ ```
439
+
440
+ #### Rule 2 — SYNOPSIS does NOT show `--`: protect operands from flag misinterpretation
441
+
442
+ **Insert `end_of_options` immediately before the first operand when any
443
+ `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, or
444
+ `custom_option` appears earlier in the same `arguments do` block.** This prevents
445
+ operand values that start with `-` from being misinterpreted as flags.
446
+
447
+ This applies even when the operand is unlikely to start with `-` in practice.
448
+ Defending against pathological inputs is the correct default.
449
+
450
+ `literal` entries are **never** the trigger for Rule 2 — regardless of whether their
451
+ value is option-style (e.g. `literal '--delete'`) or a plain subcommand word
452
+ (e.g. `literal 'remove'`). Only the five DSL option methods matter:
453
+ `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, and
454
+ `custom_option`.
455
+
456
+ ```ruby
457
+ # ✅ Correct — end_of_options guards the operand
458
+ arguments do
459
+ literal 'remote'
460
+ literal 'prune'
461
+ flag_option %i[dry_run n] # ← flag_option triggers Rule 2
462
+
463
+ end_of_options
464
+
465
+ operand :name, repeatable: true, required: true
466
+ end
467
+
468
+ # ❌ Missing end_of_options — flag as an error
469
+ arguments do
470
+ literal 'remote'
471
+ literal 'prune'
472
+ flag_option %i[dry_run n]
473
+ operand :name, repeatable: true, required: true # ← end_of_options required here
474
+ end
475
+
476
+ # ✅ Not needed — only literal entries precede the operand; no DSL option methods
477
+ arguments do
478
+ literal 'remote'
479
+ literal 'remove'
480
+ operand :name, required: true # no option methods → not required
481
+ end
482
+ ```
483
+
484
+ `end_of_options` is always safe to add even when not strictly required — it is harmless
485
+ when no operand can plausibly start with `-`. Omit it by convention when neither rule
486
+ applies: it adds no defensive value and produces unnecessarily verbose command lines
487
+ (e.g. `git remote remove -- origin` instead of `git remote remove origin`).
488
+
489
+ #### Choosing the `as:` token
490
+
491
+ `end_of_options` defaults to emitting `--` as the options terminator, which is
492
+ correct for the vast majority of git commands. However, some commands use a
493
+ different terminator token. The canonical example is `git rev-parse`, which uses
494
+ `--end-of-options` instead of `--` because `--` is a **meaningful argument** to
495
+ `rev-parse` (it separates revisions from file paths in the output), not an
496
+ options terminator.
497
+
498
+ Use `end_of_options as: '<token>'` when the git documentation for the command
499
+ explicitly documents a different terminator. Check the command's SYNOPSIS and
500
+ options section for language like "use `--end-of-options` to separate options
501
+ from arguments".
502
+
503
+ | Git documentation says | DSL form |
504
+ | --- | --- |
505
+ | `[--] <pathspec>...` or generic `--` usage | `end_of_options` (default `as: '--'`) |
506
+ | `--end-of-options` explicitly documented | `end_of_options as: '--end-of-options'` |
507
+
508
+ **Flag these as errors:**
509
+
510
+ - Using bare `end_of_options` (emitting `--`) on a command that documents
511
+ `--end-of-options` as its terminator — `--` has a different meaning for that
512
+ command and will produce incorrect behavior
513
+ - Using `end_of_options as: '--end-of-options'` on a command that does not
514
+ document it — the default `--` is correct for nearly all commands
515
+
516
+ ```ruby
517
+ # ✅ Correct — git rev-parse documents --end-of-options
518
+ end_of_options as: '--end-of-options'
519
+ operand :args, repeatable: true
520
+
521
+ # ❌ Wrong — bare -- has a different meaning in rev-parse
522
+ end_of_options
523
+ operand :args, repeatable: true
524
+ ```
525
+
526
+ ## 5. Verify modifiers
527
+
528
+ Derive `required:` and `repeatable:` directly from the SYNOPSIS notation for
529
+ operands:
530
+
531
+ | SYNOPSIS notation | `required:` | `repeatable:` |
532
+ | --- | --- | --- |
533
+ | `<arg>` | `true` | — |
534
+ | `[<arg>]` | `false` (default) | — |
535
+ | `<arg>…​` | `true` | `true` |
536
+ | `[<arg>…​]` | `false` | `true` |
537
+
538
+ Square brackets `[…]` → optional (`required: false`). Ellipsis `…​` → repeatable
539
+ (`repeatable: true`).
540
+
541
+ All valid `operand` modifiers:
542
+
543
+ | Modifier | Default | Purpose |
544
+ | --- | --- | --- |
545
+ | `required:` | `false` | Operand must be supplied by the caller |
546
+ | `repeatable:` | `false` | Operand accepts multiple values |
547
+ | `default:` | `nil` | Value emitted when the operand is absent — see note below |
548
+ | `allow_nil:` | `false` | Permits an explicit `nil` to be passed without raising |
549
+ | `skip_cli:` | `false` | Binds/validates/accesses the operand but suppresses argv emission |
550
+
551
+ **When to use `default:`**: omit it unless the explicit default value produces
552
+ different output than `nil`. For a repeatable operand, both `nil` and `[]` are
553
+ treated as absent — no args are emitted — so `default: []` is redundant and should be
554
+ left off. Only supply `default:` when you need a non-empty fallback value to be
555
+ emitted automatically (e.g. `default: 'HEAD'` on an optional commit operand).
556
+
557
+ **When to use `skip_cli:`**: use it only when an operand is part of the Ruby call
558
+ contract and should be bound/validated and available on `Bound`, but must not be
559
+ emitted to CLI argv (for example, values passed via stdin protocol). Do not use
560
+ `skip_cli:` for execution kwargs; use `execution_option` for those.
561
+
562
+ ### `execution_option` usage
563
+
564
+ `execution_option` declares a Ruby keyword argument that controls subprocess
565
+ execution rather than producing a git CLI flag. It accepts **only** a name (or array
566
+ of alias names) — no modifiers (`required:`, `as:`, `validator:`, `repeatable:`,
567
+ etc.) are supported.
568
+
569
+ Values are forwarded as Ruby kwargs to the underlying command runner (e.g.,
570
+ `command_capturing` or `command_streaming`). They never appear in the generated argv.
571
+
572
+ The authoritative set of accepted execution option names is defined by
573
+ `COMMAND_CAPTURING_ARG_DEFAULTS` and `COMMAND_STREAMING_ARG_DEFAULTS` in
574
+ `lib/git/lib.rb`. The complete set of accepted names is:
575
+
576
+ | Name | Purpose | Capturing | Streaming | Notes |
577
+ | --- | --- | --- | --- | --- |
578
+ | `:timeout` | Maximum seconds to wait for the subprocess to complete; `nil` falls back to `Git.config.timeout`, `0` disables | yes | yes | Most commonly exposed execution option |
579
+ | `:chdir` | Working directory for the subprocess | yes | yes | |
580
+ | `:out` | Output destination; when present, `Base#execute_command` selects the streaming path | yes | yes | Presence triggers streaming vs. capturing path selection |
581
+ | `:in` | IO object to use as stdin for the subprocess; must be a real IO with a file descriptor | yes | yes | |
582
+ | `:merge` | Merge stdout and stderr into a single captured string | yes | — | |
583
+ | `:env` | Additional environment variable overrides (Hash); merged with the command's own `env` by `Base#execution_opts` | yes | yes | |
584
+ | `:normalize` | Normalize captured output encoding to UTF-8 (via `rchardet` detection) | yes | — | |
585
+ | `:chomp` | Chomp trailing newlines from captured stdout and stderr | yes | — | |
586
+ | `:err` | Additional destination for stderr output; stderr is always captured internally and available via `result.stderr` — when `:err` is provided, writes are teed to both the internal buffer and this destination | yes | yes | `result.stderr` remains available even when stderr is teed to another destination; safe to expose but rarely needed |
587
+ | `:raise_on_failure` | Whether to raise `Git::FailedError` on non-zero exit status | yes | yes | `Base#execute_command` hardcodes this to `false` and uses `validate_exit_status!` instead, so exposing it via the DSL has no effect — flag as unnecessary if encountered |
588
+
589
+ An `execution_option` whose name does not appear in either defaults hash will raise
590
+ `ArgumentError` at runtime — flag it as a likely error (either a misunderstanding of
591
+ the DSL or an option that should be a `value_option` or `flag_option` instead).
592
+
593
+ Also validate that these modifiers (which do **not** apply to `operand`) are
594
+ correctly placed on their respective DSL methods:
595
+
596
+ | Modifier | Applies to |
597
+ | --- | --- |
598
+ | `as:` | `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option` — escape hatch that emits the given string verbatim; see [Section 3](#3-verify-alias-and-as-usage) for when use is justified |
599
+ | `type:` | `value_option`, `flag_or_value_option` — restrict accepted Ruby types; see [Section 2](#action-option-with-optional-value-commands) for the one valid use case (`type: [TrueClass, String]`) |
600
+ | `required:` | `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, `custom_option`, `operand` — see [Section 6](#6-check-completeness) for when to flag its absence |
601
+ | `allow_nil:` | `flag_option`, `value_option`, `flag_or_value_option`, `key_value_option`, `custom_option` (default `true`), `operand` (default `false`) — see [Section 6](#6-check-completeness) |
602
+ | `inline:` | `value_option`, `flag_or_value_option`, `key_value_option` |
603
+ | `negatable:` | `flag_option`, `flag_or_value_option` |
604
+ | `repeatable:` | `value_option`, `flag_or_value_option` — accepts an array of values (note: `operand` also accepts `repeatable:` — see operand modifier table above) |
605
+ | `allow_empty:` | `value_option` — use when git distinguishes an empty-string value (`--option ''`) from the option being absent; without it, passing `''` raises `ArgumentError` |
606
+ | `as_operand:` | `value_option` only — see pathspec table above |
607
+ | `max_times:` | `flag_option` — limits how many times the flag is emitted; caller passes an integer up to N (e.g. `force: 2` emits `--force --force`) |
608
+ | `key_separator:` | `key_value_option` — separator between key and value (default: `'='`) |
609
+
610
+ **Do not use `type:` for general type validation.** The DSL accepts any object with
611
+ a meaningful `#to_s` implementation — `String`, `Integer`, `Float`, `Pathname`,
612
+ `Symbol`, etc. — and stringifies it automatically during the build phase. Adding
613
+ `type: String` to a `value_option` rejects valid inputs like `Pathname` or `Integer`
614
+ that would produce correct CLI arguments. Git validates the actual string value; the
615
+ DSL does not need to duplicate that.
616
+
617
+ The one exception is action-option-with-optional-value commands (see
618
+ [Section 2](#action-option-with-optional-value-commands)), where
619
+ `type: [TrueClass, String]` prevents `false` from silently emitting nothing.
620
+
621
+ ## 6. Check completeness
622
+
623
+ ### YARD documentation ↔ DSL parity
624
+
625
+ Every keyword/positional parameter documented for `call` must correspond to a DSL
626
+ entry and vice versa — mismatches indicate either a missing DSL entry or stale
627
+ documentation.
628
+
629
+ **`negatable:` options require two `@option` tags.** When the DSL declares
630
+ `flag_option :foo, negatable: true` or `flag_or_value_option :foo, negatable: true`,
631
+ two separate `@option` entries are required: one for the positive key (`:foo`) and
632
+ one for the negative companion key (`:no_foo`). Both follow standard boolean
633
+ semantics (`true` emits the flag, `false`/`nil`/omitted emits nothing); both use
634
+ `(nil)` as the default value. A single merged tag or "Pass `false` for `--no-foo`" prose
635
+ does not satisfy this requirement.
636
+
637
+ ```ruby
638
+ # ❌ Missing negative companion tag
639
+ # @option options [Boolean, nil] :create_reflog (nil) create the branch's reflog
640
+
641
+ # ✅ Both forms documented with separate tags
642
+ # @option options [Boolean, nil] :create_reflog (nil) create the branch's reflog
643
+ #
644
+ # @option options [Boolean, nil] :no_create_reflog (nil) suppress branch reflog
645
+ # creation (`--no-create-reflog`)
646
+ ```
647
+
648
+ **`@option` descriptions must use the emitted long flag form.** The DSL emits the
649
+ primary (long) flag regardless of which alias the caller uses. Any `@option`
650
+ prose that references a short flag (e.g. `-v`, `-f`, `-a`) as if it is emitted
651
+ is misleading and must be corrected to the long form:
652
+
653
+ ```ruby
654
+ # ❌ Misleading — describes -v as emitted
655
+ # @option options [Boolean, Integer, nil] :verbose (nil) ...
656
+ # Pass `true` for `-v`; pass `2` for `-v -v`.
657
+
658
+ # ✅ Correct — describes the emitted flag
659
+ # @option options [Boolean, Integer, nil] :verbose (nil) ...
660
+ # Pass `true` for `--verbose`; pass `2` for `--verbose --verbose`.
661
+ ```
662
+
663
+ - If the class defines an explicit `def call`, check the YARD docs directly above
664
+ that method.
665
+ - If the class does **not** define `def call`, check the `@overload` blocks in the
666
+ class's `@!method call` YARD directive.
667
+
668
+ Flag any parameter present in an `@overload` but absent from `arguments do` (or
669
+ vice versa) as a mismatch that must be resolved.
670
+
671
+ **Example mismatch — documented `call` parameter `force:` is missing from the DSL:**
672
+
673
+ ```ruby
674
+ # @!method call
675
+ # @overload call(force: false)
676
+ # @param force [Boolean] force the operation
677
+ arguments do
678
+ # ← missing: flag_option %i[force f]
679
+ end
680
+ ```
681
+
682
+ Every option documented for the command should be represented in `arguments do`
683
+ (except those excluded due to execution-model conflicts — see [§1](#1-determine-scope-and-exclusions)),
684
+ and every DSL entry should be covered by tests.
685
+
686
+ ### Repeatable boolean flags
687
+
688
+ When the git command documentation describes a flag that can be given multiple
689
+ times to increase its effect (e.g. `--force` can be given twice for `git clean`), use
690
+ `flag_option ..., max_times: N` where N is the documented maximum repetition count.
691
+ The caller can then pass `true` (emit once) or an integer up to N (emit that many
692
+ times).
693
+
694
+ **When the docs don't state a specific maximum:** Some git docs say "can be given
695
+ more than once" without specifying a maximum. Do **not** invent a bound such as
696
+ `max_times: 2`. Instead, treat the repetition limit as requiring manual
697
+ verification against the latest-version upstream documentation and, if still
698
+ ambiguous, the latest-version upstream source. Only use `max_times: N` when that
699
+ verification establishes an explicit bound. If no explicit bound can be verified,
700
+ flag the DSL entry for manual/source verification rather than hard-coding an
701
+ arbitrary limit.
702
+
703
+ Flag these as errors:
704
+
705
+ - Using `as:` to emulate repeated flags (e.g. `as: '-ff'` or
706
+ `as: ['--force', '--force']`) instead of `max_times:`
707
+ - Using a separate symbol name for the repeated form (e.g. `:force_force`) instead of
708
+ `max_times:` on the same symbol
709
+ - Missing `max_times:` when the latest-version docs explicitly describe repeatable
710
+ flag behavior
711
+
712
+ ### Operand naming
713
+
714
+ Verify that each `operand` name matches the `<parameter>` name from the git
715
+ documentation in singular form. For example, if the git docs say
716
+ `<pathspec>...`, the operand should be named `:pathspec` (not `:pathspecs` or
717
+ `:paths`). If the docs say `<commit>`, the operand should be named `:commit`.
718
+
719
+ **Name-collision avoidance:** if an operand name conflicts with a keyword option name
720
+ in the same command (e.g., both `flag_option :commit` and `operand :commit` exist),
721
+ the **option keeps its name** and the **operand is renamed** to something close but
722
+ distinct. Prefer the plural form for repeatable operands (e.g., `:commit` →
723
+ `:commits`) or another unambiguous variant for non-repeatable ones. Flag this as an
724
+ issue if the existing operand and option share a name.
725
+
726
+ ### Per-argument validation completeness
727
+
728
+ For every `flag_option`, `value_option`, `flag_or_value_option`, and `operand`
729
+ declaration, check whether per-argument validation parameters have been considered:
730
+
731
+ | Parameter | Flag it missing if… |
732
+ | --- | --- |
733
+ | `required: true` | The command always fails without this argument, making the Ruby caller's error clearer before spawning a process |
734
+ | `allow_nil: false` | The argument is optional in Ruby (no `required: true`), but `nil` should be rejected rather than treated as "not provided" — for example, passing `nil` would produce an invalid CLI argument or ambiguous behavior |
735
+
736
+ All option DSL methods (`flag_option`, `value_option`, `flag_or_value_option`,
737
+ `key_value_option`, `custom_option`) default to `allow_nil: true`. `operand`
738
+ defaults to `allow_nil: false`. Specify `allow_nil: false` on options when nil must
739
+ be rejected explicitly. Only specify `allow_nil: true` on operands when
740
+ distinguishing an explicit `nil` from "not provided" is semantically important; do
741
+ not flag its absence when the default is correct.
742
+
743
+ **`allow_nil: true` on `required:` arguments:** flag as suspicious — allowing nil on
744
+ a required argument is rarely correct.
745
+
746
+ Do **not** flag the absence of these parameters as issues when no meaningful
747
+ constraint exists for that argument — omitting them is correct in that case.
748
+
749
+ **Cross-argument constraint methods are generally not used in command classes.** Do
750
+ not flag the absence of `conflicts`, `requires`, `requires_one_of`,
751
+ `requires_exactly_one_of`, `forbid_values`, or `allowed_values` as a completeness
752
+ issue. Command classes use per-argument validation parameters (`required:`,
753
+ `allow_nil:`, etc.) and operand format validation. Git validates its own option
754
+ semantics. There are two narrow exceptions:
755
+
756
+ 1. **Arguments git cannot observe in its argv** — the test is: does this argument
757
+ appear in git's argv? If no (e.g., `skip_cli: true` operands routed via stdin),
758
+ git cannot detect incompatibilities and constraint declarations are appropriate
759
+ and should not be flagged as policy violations. Example: `cat-file --batch`
760
+ declares `conflicts :objects, :batch_all_objects` and `requires_one_of :objects,
761
+ :batch_all_objects` because `:objects` is `skip_cli: true`.
762
+ 2. **Git-visible arguments that cause silent data loss** — if a combination of
763
+ git-visible arguments causes git to silently discard data (no error, wrong
764
+ result), a `conflicts` declaration MAY be added with: a code comment explaining
765
+ why, a reference to the git version(s) where the behavior was verified, and a
766
+ test. As of this writing, no such case has been identified.
767
+
768
+ ## 7. Check class-level declarations
769
+
770
+ The following class-level declarations are **not** part of `arguments do` but should
771
+ be verified alongside DSL entries. The canonical rules live in [Command
772
+ Implementation](../command-implementation/REFERENCE.md) — see
773
+ [Exit status guidance](../command-implementation/REFERENCE.md#exit-status-guidance)
774
+ and [`requires_git_version` convention](../command-implementation/REFERENCE.md#requires_git_version-convention). Briefly:
775
+
776
+ - **`allow_exit_status`** — present with a `Range` and rationale comment when the
777
+ command has non-zero successful exits.
778
+ - **`requires_git_version`** — present only when the command was introduced after
779
+ `Git::MINIMUM_GIT_VERSION`; uses a `'major.minor.patch'` string.
780
+ - **`` @note `arguments` block audited against https://git-scm.com/docs/git-{command}/<version> ``** —
781
+ present in the class-level YARD doc block. Flag as an error if: (1) the note is
782
+ missing, (2) the version in the URL is not the current latest git version, or
783
+ (3) the note appears in the wrong position (it must appear after all `@example`
784
+ blocks and before any `@see` tags — i.e. the canonical tag order is description
785
+ → `@example` → `@note` → `@see` → `@api private`).
786
+ To get the current latest version, run `bin/latest-git-version` from the repo root.
787
+ A stale version means the DSL may be missing options added in subsequent git
788
+ releases.