claude-dev-env 2.13.0 → 2.14.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 (241) hide show
  1. package/AGENTS.md +61 -0
  2. package/CLAUDE.md +1 -57
  3. package/_shared/AGENTS.md +15 -0
  4. package/_shared/CLAUDE.md +1 -15
  5. package/_shared/advisor/AGENTS.md +22 -0
  6. package/_shared/advisor/CLAUDE.md +1 -22
  7. package/_shared/pr-loop/AGENTS.md +27 -0
  8. package/_shared/pr-loop/CLAUDE.md +1 -27
  9. package/_shared/pr-loop/scripts/AGENTS.md +35 -0
  10. package/_shared/pr-loop/scripts/CLAUDE.md +1 -35
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +42 -0
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +1 -42
  13. package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +26 -0
  14. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -26
  15. package/_shared/pr-loop/scripts/tests/AGENTS.md +44 -0
  16. package/_shared/pr-loop/scripts/tests/CLAUDE.md +1 -44
  17. package/_shared/process-tree/AGENTS.md +41 -0
  18. package/_shared/process-tree/CLAUDE.md +1 -41
  19. package/agents/AGENTS.md +31 -0
  20. package/agents/CLAUDE.md +1 -31
  21. package/agents/clean-coder.md +6 -6
  22. package/agents/test_agent_frontmatter.py +4 -4
  23. package/audit-rubrics/AGENTS.md +43 -0
  24. package/audit-rubrics/CLAUDE.md +1 -43
  25. package/audit-rubrics/category_rubrics/AGENTS.md +37 -0
  26. package/audit-rubrics/category_rubrics/CLAUDE.md +1 -37
  27. package/audit-rubrics/prompts/AGENTS.md +37 -0
  28. package/audit-rubrics/prompts/CLAUDE.md +1 -37
  29. package/bin/AGENTS.md +112 -0
  30. package/bin/CLAUDE.md +1 -112
  31. package/bin/ever-shipped-skills.mjs +1 -0
  32. package/bin/install-constants.mjs +6 -7
  33. package/bin/install.mjs +24 -5
  34. package/bin/install.test.mjs +34 -0
  35. package/commands/AGENTS.md +23 -0
  36. package/commands/CLAUDE.md +1 -23
  37. package/commands/hook-log-extract.md +3 -3
  38. package/docs/AGENTS.md +32 -0
  39. package/docs/CLAUDE.md +1 -32
  40. package/docs/CODE_RULES.md +1 -1
  41. package/docs/imagegen.md +22 -0
  42. package/docs/references/AGENTS.md +17 -0
  43. package/docs/references/CLAUDE.md +1 -17
  44. package/hooks/AGENTS.md +31 -0
  45. package/hooks/CLAUDE.md +1 -31
  46. package/hooks/_gh_pr_author_swap_utils.py +14 -0
  47. package/hooks/advisory/AGENTS.md +16 -0
  48. package/hooks/advisory/CLAUDE.md +1 -16
  49. package/hooks/blocking/AGENTS.md +123 -0
  50. package/hooks/blocking/CLAUDE.md +1 -123
  51. package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +28 -0
  52. package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +1 -28
  53. package/hooks/blocking/code_rules_banned_identifiers.py +1 -1
  54. package/hooks/blocking/code_rules_dead_module_constant.py +149 -37
  55. package/hooks/blocking/code_rules_probe_chains.py +6 -2
  56. package/hooks/blocking/config/AGENTS.md +10 -0
  57. package/hooks/blocking/config/CLAUDE.md +1 -10
  58. package/hooks/blocking/gh_pr_author_restore.py +7 -2
  59. package/hooks/blocking/intent_only_ending_blocker.py +11 -16
  60. package/hooks/blocking/inventory_intent_records/AGENTS.md +26 -0
  61. package/hooks/blocking/inventory_intent_records/CLAUDE.md +1 -26
  62. package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +26 -0
  63. package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +1 -26
  64. package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +24 -0
  65. package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +1 -24
  66. package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +30 -0
  67. package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +1 -30
  68. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +1 -1
  69. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +171 -0
  70. package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
  71. package/hooks/blocking/test_gh_pr_author_restore.py +2 -1
  72. package/hooks/blocking/test_intent_only_ending_blocker.py +4 -0
  73. package/hooks/diagnostic/AGENTS.md +43 -0
  74. package/hooks/diagnostic/CLAUDE.md +1 -43
  75. package/hooks/diagnostic/hook_log_extractor.py +2 -2
  76. package/hooks/diagnostic/migrations/AGENTS.md +16 -0
  77. package/hooks/diagnostic/migrations/CLAUDE.md +1 -16
  78. package/hooks/diagnostic/queries/AGENTS.md +19 -0
  79. package/hooks/diagnostic/queries/CLAUDE.md +1 -19
  80. package/hooks/diagnostic/test_hook_log_extractor.py +2 -2
  81. package/hooks/git-hooks/AGENTS.md +31 -0
  82. package/hooks/git-hooks/CLAUDE.md +1 -31
  83. package/hooks/git-hooks/git_hooks_constants/AGENTS.md +21 -0
  84. package/hooks/git-hooks/git_hooks_constants/CLAUDE.md +1 -21
  85. package/hooks/git-hooks/git_hooks_constants/__init__.py +4 -6
  86. package/hooks/git-hooks/pre_push.py +47 -34
  87. package/hooks/git-hooks/test_pre_push.py +111 -7
  88. package/hooks/hooks_constants/AGENTS.md +104 -0
  89. package/hooks/hooks_constants/CLAUDE.md +1 -104
  90. package/hooks/hooks_constants/hook_log_extractor_constants.py +1 -2
  91. package/hooks/hooks_constants/task_list_loop_starter_constants.py +4 -3
  92. package/hooks/hooks_constants/working_style_prompt_constants.py +29 -20
  93. package/hooks/lifecycle/AGENTS.md +18 -0
  94. package/hooks/lifecycle/CLAUDE.md +1 -18
  95. package/hooks/observability/AGENTS.md +20 -0
  96. package/hooks/observability/CLAUDE.md +1 -20
  97. package/hooks/session/AGENTS.md +33 -0
  98. package/hooks/session/CLAUDE.md +1 -33
  99. package/hooks/session/gh_pr_author_session_cleanup.py +7 -2
  100. package/hooks/session/task_list_loop_starter.py +3 -3
  101. package/hooks/session/test_gh_pr_author_session_cleanup.py +2 -1
  102. package/hooks/session/test_task_list_loop_starter.py +10 -2
  103. package/hooks/session/test_working_style_prompt.py +9 -4
  104. package/hooks/validation/AGENTS.md +20 -0
  105. package/hooks/validation/CLAUDE.md +1 -20
  106. package/hooks/validators/AGENTS.md +51 -0
  107. package/hooks/validators/CLAUDE.md +1 -51
  108. package/hooks/validators/health_check.py +52 -19
  109. package/hooks/validators/python_style_checks.py +1 -1
  110. package/hooks/validators/test_health_check.py +112 -27
  111. package/hooks/workflow/AGENTS.md +16 -0
  112. package/hooks/workflow/CLAUDE.md +1 -16
  113. package/installable-surfaces.manifest.json +1 -0
  114. package/output-styles/AGENTS.md +15 -0
  115. package/output-styles/CLAUDE.md +1 -15
  116. package/package.json +2 -1
  117. package/rules/AGENTS.md +61 -0
  118. package/rules/CLAUDE.md +1 -61
  119. package/rules/code-standards.md +5 -9
  120. package/rules/doc-prose-cuts.md +1 -1
  121. package/scripts/AGENTS.md +62 -0
  122. package/scripts/CLAUDE.md +1 -62
  123. package/scripts/codex_compat_materializer.py +4 -0
  124. package/scripts/dev_env_scripts_constants/AGENTS.md +21 -0
  125. package/scripts/dev_env_scripts_constants/CLAUDE.md +1 -21
  126. package/scripts/sync_to_cursor/AGENTS.md +23 -0
  127. package/scripts/sync_to_cursor/CLAUDE.md +1 -23
  128. package/scripts/test_validate_instruction_pairs.py +90 -0
  129. package/scripts/test_verify_installable_package.py +5 -1
  130. package/scripts/tests/AGENTS.md +33 -0
  131. package/scripts/tests/CLAUDE.md +1 -33
  132. package/scripts/tests/test_codex_compat_materializer.py +36 -1
  133. package/scripts/validate_instruction_pairs.py +183 -0
  134. package/skills/AGENTS.md +72 -0
  135. package/skills/CLAUDE.md +1 -72
  136. package/skills/_shared/AGENTS.md +44 -0
  137. package/skills/_shared/CLAUDE.md +1 -44
  138. package/skills/_shared/advisor/AGENTS.md +9 -0
  139. package/skills/_shared/advisor/CLAUDE.md +1 -9
  140. package/skills/_shared/pr-loop/AGENTS.md +50 -0
  141. package/skills/_shared/pr-loop/CLAUDE.md +1 -50
  142. package/skills/_shared/pr-loop/prompts/AGENTS.md +9 -0
  143. package/skills/_shared/pr-loop/prompts/CLAUDE.md +1 -9
  144. package/skills/_shared/pr-loop/scripts/AGENTS.md +35 -0
  145. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -35
  146. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +24 -0
  147. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -24
  148. package/skills/anthropic-plan/AGENTS.md +34 -0
  149. package/skills/anthropic-plan/CLAUDE.md +1 -34
  150. package/skills/anthropic-plan/scripts/AGENTS.md +11 -0
  151. package/skills/anthropic-plan/scripts/CLAUDE.md +1 -11
  152. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +16 -0
  153. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/CLAUDE.md +1 -16
  154. package/skills/anthropic-plan/templates/AGENTS.md +13 -0
  155. package/skills/anthropic-plan/templates/CLAUDE.md +1 -13
  156. package/skills/anthropic-plan/workflow/AGENTS.md +14 -0
  157. package/skills/anthropic-plan/workflow/CLAUDE.md +1 -14
  158. package/skills/auditing-claude-config/AGENTS.md +21 -0
  159. package/skills/auditing-claude-config/CLAUDE.md +1 -21
  160. package/skills/autoconverge/AGENTS.md +36 -0
  161. package/skills/autoconverge/CLAUDE.md +1 -36
  162. package/skills/autoconverge/reference/AGENTS.md +16 -0
  163. package/skills/autoconverge/reference/CLAUDE.md +1 -16
  164. package/skills/autoconverge/workflow/AGENTS.md +26 -0
  165. package/skills/autoconverge/workflow/CLAUDE.md +1 -26
  166. package/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +16 -0
  167. package/skills/autoconverge/workflow/autoconverge_report_constants/CLAUDE.md +1 -16
  168. package/skills/bugteam/AGENTS.md +30 -0
  169. package/skills/bugteam/CLAUDE.md +1 -30
  170. package/skills/bugteam/reference/AGENTS.md +20 -0
  171. package/skills/bugteam/reference/CLAUDE.md +1 -20
  172. package/skills/bugteam/reference/obstacles/AGENTS.md +24 -0
  173. package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -24
  174. package/skills/bugteam/scripts/AGENTS.md +30 -0
  175. package/skills/bugteam/scripts/CLAUDE.md +1 -30
  176. package/skills/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +18 -0
  177. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +1 -18
  178. package/skills/closeout/reference/handoff-prompt-template.md +1 -1
  179. package/skills/codex-review/AGENTS.md +46 -0
  180. package/skills/codex-review/CLAUDE.md +1 -46
  181. package/skills/codex-review/reference/AGENTS.md +15 -0
  182. package/skills/codex-review/reference/CLAUDE.md +1 -15
  183. package/skills/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +18 -0
  184. package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +1 -18
  185. package/skills/codex-review/test_skill_scaffold.py +3 -3
  186. package/skills/copilot-review/AGENTS.md +18 -0
  187. package/skills/copilot-review/CLAUDE.md +1 -18
  188. package/skills/everything-search/AGENTS.md +17 -0
  189. package/skills/everything-search/CLAUDE.md +1 -17
  190. package/skills/fresh-branch/AGENTS.md +14 -0
  191. package/skills/fresh-branch/CLAUDE.md +1 -14
  192. package/skills/grok-spawn/AGENTS.md +28 -0
  193. package/skills/grok-spawn/CLAUDE.md +1 -28
  194. package/skills/pr-converge/AGENTS.md +32 -0
  195. package/skills/pr-converge/CLAUDE.md +1 -32
  196. package/skills/pr-converge/pr_converge_skill_constants/AGENTS.md +26 -0
  197. package/skills/pr-converge/pr_converge_skill_constants/CLAUDE.md +1 -26
  198. package/skills/pr-converge/reference/AGENTS.md +28 -0
  199. package/skills/pr-converge/reference/CLAUDE.md +1 -28
  200. package/skills/pr-converge/reference/obstacles/AGENTS.md +23 -0
  201. package/skills/pr-converge/reference/obstacles/CLAUDE.md +1 -23
  202. package/skills/pr-converge/scripts/AGENTS.md +46 -0
  203. package/skills/pr-converge/scripts/CLAUDE.md +1 -46
  204. package/skills/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +18 -0
  205. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -18
  206. package/skills/pr-converge/workflows/AGENTS.md +16 -0
  207. package/skills/pr-converge/workflows/CLAUDE.md +1 -16
  208. package/skills/prototype/reference/honest-limitations.md +1 -1
  209. package/skills/rebase/AGENTS.md +32 -0
  210. package/skills/rebase/CLAUDE.md +1 -32
  211. package/skills/recall/AGENTS.md +30 -0
  212. package/skills/recall/CLAUDE.md +1 -30
  213. package/skills/remember/AGENTS.md +31 -0
  214. package/skills/remember/CLAUDE.md +1 -31
  215. package/skills/reviews/SKILL.md +1 -1
  216. package/skills/session-log/AGENTS.md +32 -0
  217. package/skills/session-log/CLAUDE.md +1 -32
  218. package/skills/session-tidy/AGENTS.md +36 -0
  219. package/skills/session-tidy/CLAUDE.md +1 -36
  220. package/skills/skill-builder/AGENTS.md +49 -0
  221. package/skills/skill-builder/CLAUDE.md +1 -49
  222. package/skills/skill-builder/references/AGENTS.md +25 -0
  223. package/skills/skill-builder/references/CLAUDE.md +1 -25
  224. package/skills/skill-builder/references/deterministic-elements.md +1 -1
  225. package/skills/skill-builder/templates/AGENTS.md +14 -0
  226. package/skills/skill-builder/templates/CLAUDE.md +1 -14
  227. package/skills/skill-builder/workflows/AGENTS.md +19 -0
  228. package/skills/skill-builder/workflows/CLAUDE.md +1 -19
  229. package/skills/task-build/AGENTS.md +29 -0
  230. package/skills/task-build/CLAUDE.md +1 -29
  231. package/skills/update/AGENTS.md +38 -0
  232. package/skills/update/CLAUDE.md +1 -38
  233. package/system-prompts/AGENTS.md +17 -0
  234. package/system-prompts/CLAUDE.md +1 -17
  235. package/system-prompts/software-engineer.xml +3 -3
  236. package/skills/imagegen/SKILL.md +0 -21
  237. package/skills/imagegen/scripts/config/__init__.py +0 -1
  238. package/skills/imagegen/scripts/config/constants.py +0 -28
  239. package/skills/imagegen/scripts/imagegen.py +0 -45
  240. package/skills/imagegen/scripts/imagegen_core.py +0 -528
  241. package/skills/imagegen/scripts/test_imagegen.py +0 -204
@@ -0,0 +1,19 @@
1
+ # hooks/diagnostic/queries
2
+
3
+ Parameterized SQL queries for inspecting the `hook_events` Neon table. Run these directly against the Neon database to analyze hook-firing patterns.
4
+
5
+ ## Files
6
+
7
+ | File | What it returns |
8
+ |---|---|
9
+ | `block_details_for_hook.sql` | Full details for all blocked events matching a given hook name |
10
+ | `blocks_by_category.sql` | Count of blocks grouped by hook category |
11
+ | `blocks_by_tool.sql` | Count of blocks grouped by tool name |
12
+ | `blocks_last_7_days.sql` | All blocked events from the last 7 days |
13
+ | `top_blockers_last_24_hours.sql` | Hook names with the most blocks in the last 24 hours |
14
+ | `top_blockers_overall.sql` | Hook names with the most blocks across all time |
15
+
16
+ ## Conventions
17
+
18
+ - Queries target the `hook_events` table and `blocked_commands` view defined in `diagnostic/schema.sql`.
19
+ - Run with `psql $DATABASE_URL -f <query>.sql` or paste into the Neon console.
@@ -1,19 +1 @@
1
- # hooks/diagnostic/queries
2
-
3
- Parameterized SQL queries for inspecting the `hook_events` Neon table. Run these directly against the Neon database to analyze hook-firing patterns.
4
-
5
- ## Files
6
-
7
- | File | What it returns |
8
- |---|---|
9
- | `block_details_for_hook.sql` | Full details for all blocked events matching a given hook name |
10
- | `blocks_by_category.sql` | Count of blocks grouped by hook category |
11
- | `blocks_by_tool.sql` | Count of blocks grouped by tool name |
12
- | `blocks_last_7_days.sql` | All blocked events from the last 7 days |
13
- | `top_blockers_last_24_hours.sql` | Hook names with the most blocks in the last 24 hours |
14
- | `top_blockers_overall.sql` | Hook names with the most blocks across all time |
15
-
16
- ## Conventions
17
-
18
- - Queries target the `hook_events` table and `blocked_commands` view defined in `diagnostic/schema.sql`.
19
- - Run with `psql $DATABASE_URL -f <query>.sql` or paste into the Neon console.
1
+ @AGENTS.md
@@ -617,7 +617,7 @@ def test_tool_name_extracted_from_hook_name_prefix() -> None:
617
617
  assert hook_log_extractor.extract_tool_name("UserPromptSubmit") is None
618
618
 
619
619
 
620
- def test_run_summary_prints_no_new_blocks_when_cursor_empty(
620
+ def test_run_summary_prints_zero_blocked_events_when_cursor_empty(
621
621
  capsys: pytest.CaptureFixture[str],
622
622
  ) -> None:
623
623
  fake_cursor = MagicMock()
@@ -632,7 +632,7 @@ def test_run_summary_prints_no_new_blocks_when_cursor_empty(
632
632
 
633
633
  captured = capsys.readouterr()
634
634
  assert exit_code == 0
635
- assert "No new blocks since last run." in captured.out
635
+ assert "Blocked events recorded in the last 24 hours: 0." in captured.out
636
636
 
637
637
 
638
638
  def test_run_summary_prints_table_when_rows_returned(
@@ -0,0 +1,31 @@
1
+ # hooks/git-hooks
2
+
3
+ Native git hooks that run outside the Claude Code lifecycle — invoked directly by git at commit and push time. The installer copies these scripts into the user's shared git-hooks directory (`core.hooksPath`).
4
+
5
+ ## Key files
6
+
7
+ | File | Git hook | What it does |
8
+ |---|---|---|
9
+ | `pre_commit.py` | `pre-commit` | Runs the CODE_RULES gate (`precommit_code_rules_gate.py`) over staged changes; exits 1 when any staged file has a blocking violation |
10
+ | `pre_push.py` | `pre-push` | Blocks a push that would land a non-`main` local branch onto remote `main` (or `master`), then runs the CODE_RULES gate. An existing branch's gate base is the merge base with the remote default branch; the gate process still diffs that base against checkout HEAD, so the surface matches the pushed tip only when HEAD is that tip. |
11
+ | `pre_push_base_reference.py` | — | Resolves a usable gate base for `pre_push.py`: reads the pushed remote name from git's arguments, then turns a symbolic default-branch head into a reference that git can resolve |
12
+ | `post_commit.py` | `post-commit` | Runs after a commit lands; performs any post-commit bookkeeping |
13
+ | `gate_utils.py` | — | Shared helpers: resolves the gate script path, checks that the path is a safe regular file |
14
+ | `test_config.py` | — | Test configuration helpers |
15
+ | `test_gate_utils.py` | — | Tests for `gate_utils.py` |
16
+ | `test_pre_commit.py` | — | Tests for `pre_commit.py` |
17
+ | `test_pre_push.py` | — | Tests for `pre_push.py` |
18
+ | `test_pre_push_base_reference.py` | — | Tests for the gate base resolution in `pre_push.py` |
19
+ | `conftest.py` | — | Puts this directory on `sys.path` and clears cached `config` modules, so each test module imports the hooks at the top of the file |
20
+
21
+ ## Subdirectory
22
+
23
+ | Directory | Role |
24
+ |---|---|
25
+ | `git_hooks_constants/` | Shared constants imported by the git-hook scripts |
26
+
27
+ ## Conventions
28
+
29
+ - The installer strips the `_` and `.py` suffix when copying into the live git-hooks path (e.g. `pre_commit.py` becomes `pre-commit`).
30
+ - Constants (exit codes, argument names, error messages) live in `git_hooks_constants/` and are imported at the top of each script.
31
+ - Run tests with `python -m pytest git-hooks/test_<name>.py`.
@@ -1,31 +1 @@
1
- # hooks/git-hooks
2
-
3
- Native git hooks that run outside the Claude Code lifecycle — invoked directly by git at commit and push time. The installer copies these scripts into the user's shared git-hooks directory (`core.hooksPath`).
4
-
5
- ## Key files
6
-
7
- | File | Git hook | What it does |
8
- |---|---|---|
9
- | `pre_commit.py` | `pre-commit` | Runs the CODE_RULES gate (`precommit_code_rules_gate.py`) over staged changes; exits 1 when any staged file has a blocking violation |
10
- | `pre_push.py` | `pre-push` | Blocks a push that would land a non-`main` local branch onto remote `main` (or `master`), then runs the CODE_RULES gate. An existing branch's gate base is the merge base with the remote default branch; the gate process still diffs that base against checkout HEAD, so the surface matches the pushed tip only when HEAD is that tip. |
11
- | `pre_push_base_reference.py` | — | Resolves a usable gate base for `pre_push.py`: reads the pushed remote name from git's arguments, then turns a symbolic default-branch head into a reference that git can resolve |
12
- | `post_commit.py` | `post-commit` | Runs after a commit lands; performs any post-commit bookkeeping |
13
- | `gate_utils.py` | — | Shared helpers: resolves the gate script path, checks that the path is a safe regular file |
14
- | `test_config.py` | — | Test configuration helpers |
15
- | `test_gate_utils.py` | — | Tests for `gate_utils.py` |
16
- | `test_pre_commit.py` | — | Tests for `pre_commit.py` |
17
- | `test_pre_push.py` | — | Tests for `pre_push.py` |
18
- | `test_pre_push_base_reference.py` | — | Tests for the gate base resolution in `pre_push.py` |
19
- | `conftest.py` | — | Puts this directory on `sys.path` and clears cached `config` modules, so each test module imports the hooks at the top of the file |
20
-
21
- ## Subdirectory
22
-
23
- | Directory | Role |
24
- |---|---|
25
- | `git_hooks_constants/` | Shared constants imported by the git-hook scripts |
26
-
27
- ## Conventions
28
-
29
- - The installer strips the `_` and `.py` suffix when copying into the live git-hooks path (e.g. `pre_commit.py` becomes `pre-commit`).
30
- - Constants (exit codes, argument names, error messages) live in `git_hooks_constants/` and are imported at the top of each script.
31
- - Run tests with `python -m pytest git-hooks/test_<name>.py`.
1
+ @AGENTS.md
@@ -0,0 +1,21 @@
1
+ # hooks/git-hooks/git_hooks_constants
2
+
3
+ Shared constants imported by the git-hook scripts in `git-hooks/`. Centralizes exit codes, argument names, and error messages so every tunable lives in one place.
4
+
5
+ ## Files
6
+
7
+ | File | Contents |
8
+ |---|---|
9
+ | `__init__.py` | Exports all constants; marks this as a package so `from git_hooks_constants import ...` resolves |
10
+
11
+ ## Key constants (defined in `__init__.py`)
12
+
13
+ - `GATE_INFRASTRUCTURE_FAILURE_EXIT_CODE` — exit code when the gate script cannot be found or launched
14
+ - `GATE_SCRIPT_NOT_FOUND_MESSAGE` — error message when the gate script path does not exist
15
+ - `INVOKE_GATE_FAILURE_MESSAGE` — error message when the gate subprocess fails to start
16
+ - `STAGED_SCOPE_ARGUMENT` — CLI argument passed to the gate script to scope it to staged changes
17
+
18
+ ## Conventions
19
+
20
+ - Import with `from git_hooks_constants import <CONSTANT>` from within the `git-hooks/` directory.
21
+ - Add new constants here rather than inline in the hook scripts.
@@ -1,21 +1 @@
1
- # hooks/git-hooks/git_hooks_constants
2
-
3
- Shared constants imported by the git-hook scripts in `git-hooks/`. Centralizes exit codes, argument names, and error messages so every tunable lives in one place.
4
-
5
- ## Files
6
-
7
- | File | Contents |
8
- |---|---|
9
- | `__init__.py` | Exports all constants; marks this as a package so `from git_hooks_constants import ...` resolves |
10
-
11
- ## Key constants (defined in `__init__.py`)
12
-
13
- - `GATE_INFRASTRUCTURE_FAILURE_EXIT_CODE` — exit code when the gate script cannot be found or launched
14
- - `GATE_SCRIPT_NOT_FOUND_MESSAGE` — error message when the gate script path does not exist
15
- - `INVOKE_GATE_FAILURE_MESSAGE` — error message when the gate subprocess fails to start
16
- - `STAGED_SCOPE_ARGUMENT` — CLI argument passed to the gate script to scope it to staged changes
17
-
18
- ## Conventions
19
-
20
- - Import with `from git_hooks_constants import <CONSTANT>` from within the `git-hooks/` directory.
21
- - Add new constants here rather than inline in the hook scripts.
1
+ @AGENTS.md
@@ -49,12 +49,9 @@ NO_PARSEABLE_STDIN_LINES_MESSAGE: str = (
49
49
  NO_PARSEABLE_STDIN_LINES_SENTINEL: str = "__no_parseable_stdin_lines__"
50
50
  UNRESOLVABLE_MERGE_BASE_SENTINEL: str = "__unresolvable_merge_base__"
51
51
  UNRESOLVABLE_MERGE_BASE_MESSAGE: str = (
52
- "claude-dev-env pre-push: git found no merge base between the pushed object "
53
- "and the default branch, so the gate scope is unknown and "
54
- "enforcement is skipped. That happens when the pushed branch and the "
55
- "default branch hold unrelated histories, or when a default-branch ref "
56
- "resolved and merge-base still could not name a shared commit "
57
- "(for example a shallow clone missing the connecting history)."
52
+ "claude-dev-env pre-push: CODE_RULES validation is pending because the pushed "
53
+ "object and default branch have no shared merge base. Restore shared history "
54
+ "or provide a reachable default branch, then run the push again."
58
55
  )
59
56
  LOCAL_BRANCH_REFERENCE_PREFIX: str = "refs/heads/"
60
57
  ORIGIN_HEAD_SYMBOLIC_REFERENCE: str = "refs/remotes/origin/HEAD"
@@ -63,6 +60,7 @@ ALL_DEFAULT_BRANCH_FALLBACK_REFERENCES: tuple[str, ...] = (
63
60
  "refs/remotes/origin/main",
64
61
  "refs/remotes/origin/master",
65
62
  )
63
+ REMOTE_BRANCH_REFERENCE_TEMPLATE: str = "refs/remotes/{remote}/{branch}"
66
64
  ALL_GIT_SYMBOLIC_REFERENCE_COMMAND_PREFIX: tuple[str, ...] = (
67
65
  "git",
68
66
  "symbolic-ref",
@@ -22,9 +22,8 @@ object name is the base when no default-branch ref resolves and when the
22
22
  pushed branch is the default branch. When every remote object name is zero
23
23
  (new branch) or stdin is empty, the gate falls back to the remote's default
24
24
  branch symbolic ref. When a default-branch ref resolves and git still reports
25
- no merge base, the base is unknown, so CODE_RULES enforcement is skipped with
26
- a printed reason rather than scoped to the stdin remote object name, which on
27
- a rebased tip would re-flag replayed commits.
25
+ no merge base, the base stays pending and the hook reports the infrastructure
26
+ status with a printed reason, preserving the scope boundary for rebased tips.
28
27
 
29
28
  Gate scope: the gate process this hook launches diffs its `--base` against the
30
29
  current checkout's HEAD, so the surface is the commits between that base and
@@ -32,14 +31,13 @@ HEAD. A push whose pushed object is HEAD reads as the branch's own work; a
32
31
  push of any other object is still scoped to HEAD.
33
32
 
34
33
  Exit codes:
35
- 0 - the push destination is allowed and its commits pass the gate (or
36
- the gate is not installed, or CODE_RULES is skipped because a
37
- default-branch ref resolved and git still reported no merge base).
34
+ 0 - the push destination is allowed and its commits pass the gate, or the
35
+ gate is not installed.
38
36
  1 - the push would land a non-protected local branch onto a protected
39
37
  remote branch, or a commit introduces a blocking violation.
40
- 2 - unexpected invocation failure (e.g., subprocess could not launch),
41
- stdin carried no parseable line, or a new-branch/empty-stdin base
42
- could not be turned into a usable commit name.
38
+ 2 - infrastructure attention is required because a subprocess could not
39
+ launch, stdin carried no parseable line, a base could not be resolved,
40
+ or a usable commit name could not be produced.
43
41
  """
44
42
 
45
43
  from __future__ import annotations
@@ -49,8 +47,9 @@ import sys
49
47
  from pathlib import Path
50
48
  from typing import NamedTuple
51
49
 
50
+ from gate_utils import is_safe_regular_file, resolve_gate_script_path
52
51
  from git_hooks_constants import (
53
- ALL_DEFAULT_BRANCH_FALLBACK_REFERENCES,
52
+ ALL_FALLBACK_REMOTE_DEFAULT_BRANCH_NAMES,
54
53
  ALL_GIT_MERGE_BASE_COMMAND_PREFIX,
55
54
  ALL_GIT_SYMBOLIC_REFERENCE_COMMAND_PREFIX,
56
55
  ALL_GIT_VERIFY_REFERENCE_COMMAND_PREFIX,
@@ -58,6 +57,7 @@ from git_hooks_constants import (
58
57
  ALL_ZEROS_OBJECT_NAME_CHARACTER,
59
58
  BASE_REFERENCE_ARGUMENT,
60
59
  DEFAULT_REMOTE_BASE_REFERENCE,
60
+ DEFAULT_REMOTE_NAME,
61
61
  GATE_INFRASTRUCTURE_FAILURE_EXIT_CODE,
62
62
  GIT_COMMAND_TIMEOUT_SECONDS,
63
63
  GIT_COMMAND_UNAVAILABLE_MESSAGE,
@@ -72,11 +72,10 @@ from git_hooks_constants import (
72
72
  MALFORMED_STDIN_LINE_MESSAGE,
73
73
  NO_PARSEABLE_STDIN_LINES_MESSAGE,
74
74
  NO_PARSEABLE_STDIN_LINES_SENTINEL,
75
- ORIGIN_HEAD_SYMBOLIC_REFERENCE,
76
- ORIGIN_REMOTE_TRACKING_REFERENCE_PREFIX,
77
75
  PRE_PUSH_GATE_SCRIPT_NOT_FOUND_MESSAGE,
78
76
  PROTECTED_BRANCH_PUSH_BLOCK_EXIT_CODE,
79
77
  PROTECTED_BRANCH_PUSH_BLOCK_MESSAGE,
78
+ REMOTE_BRANCH_REFERENCE_TEMPLATE,
80
79
  REMOTE_REFERENCE_FIELD_INDEX,
81
80
  STDIN_LINE_FIELD_COUNT,
82
81
  STDIN_READ_FAILURE_MESSAGE,
@@ -84,7 +83,6 @@ from git_hooks_constants import (
84
83
  UNRESOLVABLE_MERGE_BASE_MESSAGE,
85
84
  UNRESOLVABLE_MERGE_BASE_SENTINEL,
86
85
  )
87
- from gate_utils import is_safe_regular_file, resolve_gate_script_path
88
86
  from pre_push_base_reference import (
89
87
  resolve_remote_name_from_arguments,
90
88
  resolve_usable_base_reference,
@@ -294,24 +292,24 @@ def run_git_reference_query(all_git_arguments: tuple[str, ...]) -> str | None:
294
292
  return decoded_output.strip() or None
295
293
 
296
294
 
297
- def resolve_default_branch_reference() -> str | None:
295
+ def resolve_default_branch_reference(remote_name: str = DEFAULT_REMOTE_NAME) -> str | None:
298
296
  """Return the remote-tracking ref name of the remote's default branch.
299
297
 
300
- The remote's HEAD symbolic ref answers first. When it is unset, each
301
- candidate default-branch remote-tracking ref answers in turn.
298
+ The remote's HEAD symbolic ref answers first. When it is unset, the
299
+ supported default-branch tracking refs answer in turn.
302
300
 
303
- Resolution reads ``origin`` alone and never the remote name git passes as
304
- argv[1], and its unset-``origin/HEAD`` fallback prefers ``origin/main``
305
- over ``origin/master``, so a repo whose default is another branch beside a
306
- legacy ``main`` resolves the wrong base ΓÇö the assumption set tracked at
307
- ~/.claude/orchestrator-runs/falsify-first/parked-items.md row 3.
301
+ Args:
302
+ remote_name: Remote whose default branch provides the gate base.
308
303
 
309
304
  Returns:
310
305
  A remote-tracking ref name, or None when no default-branch ref
311
306
  resolves.
312
307
  """
308
+ remote_head_reference = REMOTE_BRANCH_REFERENCE_TEMPLATE.format(
309
+ remote=remote_name, branch="HEAD"
310
+ )
313
311
  symbolic_reference = run_git_reference_query(
314
- (*ALL_GIT_SYMBOLIC_REFERENCE_COMMAND_PREFIX, ORIGIN_HEAD_SYMBOLIC_REFERENCE)
312
+ (*ALL_GIT_SYMBOLIC_REFERENCE_COMMAND_PREFIX, remote_head_reference)
315
313
  )
316
314
  if symbolic_reference is not None:
317
315
  verified_symbolic_target = run_git_reference_query(
@@ -319,7 +317,10 @@ def resolve_default_branch_reference() -> str | None:
319
317
  )
320
318
  if verified_symbolic_target is not None:
321
319
  return symbolic_reference
322
- for each_candidate_reference in ALL_DEFAULT_BRANCH_FALLBACK_REFERENCES:
320
+ for each_branch_name in ALL_FALLBACK_REMOTE_DEFAULT_BRANCH_NAMES:
321
+ each_candidate_reference = REMOTE_BRANCH_REFERENCE_TEMPLATE.format(
322
+ remote=remote_name, branch=each_branch_name
323
+ )
323
324
  verified_reference = run_git_reference_query(
324
325
  (*ALL_GIT_VERIFY_REFERENCE_COMMAND_PREFIX, each_candidate_reference)
325
326
  )
@@ -360,7 +361,9 @@ def find_branch_update_push(stdin_text: str) -> PushLine | None:
360
361
 
361
362
 
362
363
  def resolve_default_branch_merge_base(
363
- remote_branch_name: str, pushed_object_name: str
364
+ remote_branch_name: str,
365
+ pushed_object_name: str,
366
+ remote_name: str = DEFAULT_REMOTE_NAME,
364
367
  ) -> str | None:
365
368
  """Return the merge base of the pushed object and the default branch.
366
369
 
@@ -371,6 +374,7 @@ def resolve_default_branch_merge_base(
371
374
  Args:
372
375
  remote_branch_name: The remote branch the push updates.
373
376
  pushed_object_name: The object name the push carries.
377
+ remote_name: The remote whose default branch provides the gate base.
374
378
 
375
379
  Returns:
376
380
  The merge-base object name; None when no default-branch ref resolves or
@@ -378,12 +382,13 @@ def resolve_default_branch_merge_base(
378
382
  no merge base applies; or the unresolvable-merge-base sentinel when a
379
383
  default-branch ref resolves and git still reports no merge base.
380
384
  """
381
- default_branch_reference = resolve_default_branch_reference()
385
+ default_branch_reference = resolve_default_branch_reference(remote_name)
382
386
  if default_branch_reference is None:
383
387
  return None
384
- default_branch_name = default_branch_reference.removeprefix(
385
- ORIGIN_REMOTE_TRACKING_REFERENCE_PREFIX
388
+ default_branch_prefix = REMOTE_BRANCH_REFERENCE_TEMPLATE.format(
389
+ remote=remote_name, branch=""
386
390
  )
391
+ default_branch_name = default_branch_reference.removeprefix(default_branch_prefix)
387
392
  if remote_branch_name == default_branch_name:
388
393
  return None
389
394
  # Anchors to the pushed object; deferring to the gate's HEAD-based merge-base would change behavior on non-HEAD pushes.
@@ -399,7 +404,9 @@ def resolve_default_branch_merge_base(
399
404
  return merge_base_object_name
400
405
 
401
406
 
402
- def resolve_gate_base_reference(stdin_text: str) -> str | None:
407
+ def resolve_gate_base_reference(
408
+ stdin_text: str, remote_name: str = DEFAULT_REMOTE_NAME
409
+ ) -> str | None:
403
410
  """Return the reference the gate scopes its changed lines to.
404
411
 
405
412
  A branch update reads from the merge base with the remote default branch,
@@ -414,20 +421,24 @@ def resolve_gate_base_reference(stdin_text: str) -> str | None:
414
421
 
415
422
  Args:
416
423
  stdin_text: The pre-push stdin payload.
424
+ remote_name: The remote whose default branch provides the gate base.
417
425
 
418
426
  Returns:
419
427
  The gate's base reference, the no-parseable-lines sentinel, the
420
428
  unresolvable-merge-base sentinel, or None when the push only deletes
421
429
  remote branches.
422
430
  """
423
- return _resolve_gate_base_from_parsed(_parse_push_stdin(stdin_text))
431
+ return _resolve_gate_base_from_parsed(_parse_push_stdin(stdin_text), remote_name)
424
432
 
425
433
 
426
- def _resolve_gate_base_from_parsed(parsed_stdin: ParsedPushStdin) -> str | None:
434
+ def _resolve_gate_base_from_parsed(
435
+ parsed_stdin: ParsedPushStdin, remote_name: str = DEFAULT_REMOTE_NAME
436
+ ) -> str | None:
427
437
  """Return the gate's base reference for an already-parsed stdin payload.
428
438
 
429
439
  Args:
430
440
  parsed_stdin: The parsed stdin payload.
441
+ remote_name: The remote whose default branch provides the gate base.
431
442
 
432
443
  Returns:
433
444
  The merge base with the remote default branch for a branch update, the
@@ -440,7 +451,9 @@ def _resolve_gate_base_from_parsed(parsed_stdin: ParsedPushStdin) -> str | None:
440
451
  if branch_update is None:
441
452
  return _resolve_base_reference_from_lines(parsed_stdin)
442
453
  merge_base_object_name = resolve_default_branch_merge_base(
443
- branch_update.remote_branch_name, branch_update.local_object_name
454
+ branch_update.remote_branch_name,
455
+ branch_update.local_object_name,
456
+ remote_name,
444
457
  )
445
458
  if merge_base_object_name is None:
446
459
  return branch_update.remote_object_name
@@ -542,7 +555,8 @@ def main() -> int:
542
555
  file=sys.stderr,
543
556
  )
544
557
  return 0
545
- base_reference = _resolve_gate_base_from_parsed(parsed_stdin)
558
+ remote_name = resolve_remote_name_from_arguments(sys.argv)
559
+ base_reference = _resolve_gate_base_from_parsed(parsed_stdin, remote_name)
546
560
  if base_reference is None:
547
561
  return 0
548
562
  if base_reference == no_parseable_stdin_lines_sentinel:
@@ -550,8 +564,7 @@ def main() -> int:
550
564
  return gate_infrastructure_failure_exit_code
551
565
  if base_reference == unresolvable_merge_base_sentinel:
552
566
  print(unresolvable_merge_base_message, file=sys.stderr)
553
- return 0
554
- remote_name = resolve_remote_name_from_arguments(sys.argv)
567
+ return gate_infrastructure_failure_exit_code
555
568
  try:
556
569
  usable_base_reference = resolve_usable_base_reference(
557
570
  base_reference, remote_name, run_git_text_command
@@ -156,11 +156,10 @@ def test_run_git_reference_query_returns_replaced_text_on_locale_invalid_bytes(
156
156
  assert resolved_output == expected_replaced_reference
157
157
 
158
158
 
159
- def test_unresolvable_merge_base_message_does_not_claim_dangling_origin_head() -> None:
159
+ def test_unresolvable_merge_base_message_describes_pending_validation() -> None:
160
160
  skip_message = git_hooks_constants.UNRESOLVABLE_MERGE_BASE_MESSAGE
161
- assert "origin/HEAD names a ref" not in skip_message
162
- assert "unrelated histories" in skip_message
163
- assert "merge-base still could not name a shared commit" in skip_message
161
+ assert "CODE_RULES validation is pending" in skip_message
162
+ assert "Restore shared history" in skip_message
164
163
 
165
164
 
166
165
  def test_run_git_text_command_raises_when_git_cannot_launch(
@@ -239,6 +238,11 @@ def resolve_remote_head_reference(monkeypatch: pytest.MonkeyPatch) -> None:
239
238
  return GIT_RESOLVED_EXIT_CODE, RESOLVED_COMMIT_OBJECT_NAME
240
239
 
241
240
  monkeypatch.setattr(pre_push, "run_git_text_command", fake_run_git_text_command)
241
+ monkeypatch.setattr(
242
+ sys,
243
+ "argv",
244
+ [HOOK_INVOCATION_NAME, git_hooks_constants.DEFAULT_REMOTE_NAME, PUSHED_REMOTE_URL],
245
+ )
242
246
 
243
247
 
244
248
 
@@ -994,6 +998,46 @@ def test_resolve_default_branch_reference_reads_the_origin_head_symbolic_referen
994
998
  assert pre_push.resolve_default_branch_reference() == ORIGIN_DEFAULT_BRANCH_REFERENCE
995
999
 
996
1000
 
1001
+ def test_resolve_default_branch_reference_reads_the_requested_remote_head(
1002
+ tmp_path: Path,
1003
+ monkeypatch: pytest.MonkeyPatch,
1004
+ ) -> None:
1005
+ repository, default_branch_tip, _topic_branch_tip = _build_default_branch_repository(
1006
+ tmp_path
1007
+ )
1008
+ upstream_reference = "refs/remotes/upstream/main"
1009
+ upstream_head_reference = "refs/remotes/upstream/HEAD"
1010
+ _run_fixture_git(repository, "update-ref", upstream_reference, default_branch_tip)
1011
+ _run_fixture_git(
1012
+ repository, "symbolic-ref", upstream_head_reference, upstream_reference
1013
+ )
1014
+ monkeypatch.chdir(repository)
1015
+
1016
+ assert pre_push.resolve_default_branch_reference("upstream") == upstream_reference
1017
+
1018
+
1019
+ def test_resolve_default_branch_merge_base_uses_requested_remote_trunk_fallback(
1020
+ tmp_path: Path,
1021
+ monkeypatch: pytest.MonkeyPatch,
1022
+ ) -> None:
1023
+ repository, default_branch_tip, topic_branch_tip = _build_default_branch_repository(
1024
+ tmp_path
1025
+ )
1026
+ upstream_trunk_reference = "refs/remotes/upstream/trunk"
1027
+ _run_fixture_git(
1028
+ repository, "update-ref", upstream_trunk_reference, default_branch_tip
1029
+ )
1030
+ monkeypatch.chdir(repository)
1031
+
1032
+ merge_base_object_name = pre_push.resolve_default_branch_merge_base(
1033
+ FEATURE_BRANCH_NAME,
1034
+ topic_branch_tip,
1035
+ "upstream",
1036
+ )
1037
+
1038
+ assert merge_base_object_name == default_branch_tip
1039
+
1040
+
997
1041
  def test_resolve_default_branch_reference_returns_none_without_remote_tracking_refs(
998
1042
  tmp_path: Path,
999
1043
  monkeypatch: pytest.MonkeyPatch,
@@ -1198,7 +1242,7 @@ def test_main_allows_the_push_when_origin_head_names_an_absent_reference(
1198
1242
  assert recorded_arguments == ["--base", remote_object_name]
1199
1243
 
1200
1244
 
1201
- def test_main_skips_the_gate_when_no_merge_base_resolves(
1245
+ def test_main_keeps_the_gate_pending_when_no_merge_base_resolves(
1202
1246
  tmp_path: Path,
1203
1247
  monkeypatch: pytest.MonkeyPatch,
1204
1248
  capsys: pytest.CaptureFixture[str],
@@ -1230,13 +1274,73 @@ def test_main_skips_the_gate_when_no_merge_base_resolves(
1230
1274
 
1231
1275
  exit_code = pre_push.main()
1232
1276
 
1233
- assert exit_code == 0
1277
+ assert exit_code == git_hooks_constants.GATE_INFRASTRUCTURE_FAILURE_EXIT_CODE
1234
1278
  assert not recorded_arguments_path.exists(), (
1235
- "the gate ran on a base the hook could not verify"
1279
+ "the gate remains pending until the hook verifies a usable base"
1236
1280
  )
1237
1281
  assert git_hooks_constants.UNRESOLVABLE_MERGE_BASE_MESSAGE in capsys.readouterr().err
1238
1282
 
1239
1283
 
1284
+ def test_main_uses_the_pushed_remote_when_origin_has_unrelated_history(
1285
+ tmp_path: Path,
1286
+ monkeypatch: pytest.MonkeyPatch,
1287
+ ) -> None:
1288
+ repository, default_branch_tip, topic_branch_tip = _build_default_branch_repository(
1289
+ tmp_path
1290
+ )
1291
+ _run_fixture_git(repository, "checkout", "--orphan", "unrelated-origin")
1292
+ _run_fixture_git(repository, "rm", "-rf", ".")
1293
+ unrelated_origin_tip = _commit_file(repository, "unrelated.txt")
1294
+ _run_fixture_git(repository, "checkout", FEATURE_BRANCH_NAME)
1295
+ _run_fixture_git(
1296
+ repository,
1297
+ "update-ref",
1298
+ ORIGIN_DEFAULT_BRANCH_REFERENCE,
1299
+ unrelated_origin_tip,
1300
+ )
1301
+ _set_origin_head(repository, ORIGIN_DEFAULT_BRANCH_REFERENCE)
1302
+ upstream_reference = "refs/remotes/upstream/main"
1303
+ upstream_head_reference = "refs/remotes/upstream/HEAD"
1304
+ _run_fixture_git(repository, "update-ref", upstream_reference, default_branch_tip)
1305
+ _run_fixture_git(
1306
+ repository, "symbolic-ref", upstream_head_reference, upstream_reference
1307
+ )
1308
+ recorded_arguments_path = tmp_path / "upstream_recorded_arguments.txt"
1309
+ recording_gate_script_path = tmp_path / "upstream_recording_gate.py"
1310
+ recording_gate_script_path.write_text(
1311
+ "import pathlib, sys\n"
1312
+ f'pathlib.Path(r"{recorded_arguments_path}").write_text('
1313
+ "'\\n'.join(sys.argv[1:]), encoding='utf-8')\n"
1314
+ "sys.exit(0)\n",
1315
+ encoding="utf-8",
1316
+ )
1317
+ monkeypatch.setenv("CODE_RULES_GATE_PATH", str(recording_gate_script_path))
1318
+ monkeypatch.chdir(repository)
1319
+ monkeypatch.setattr(
1320
+ sys, "argv", [HOOK_INVOCATION_NAME, PUSHED_REMOTE_NAME, PUSHED_REMOTE_URL]
1321
+ )
1322
+ monkeypatch.setattr(
1323
+ sys,
1324
+ "stdin",
1325
+ io.StringIO(
1326
+ _push_stdin_line(
1327
+ FEATURE_BRANCH_NAME,
1328
+ topic_branch_tip,
1329
+ FEATURE_BRANCH_NAME,
1330
+ NON_ZERO_REMOTE_SHA_ONE,
1331
+ )
1332
+ ),
1333
+ )
1334
+
1335
+ exit_code = pre_push.main()
1336
+
1337
+ assert exit_code == 0
1338
+ assert recorded_arguments_path.read_text(encoding="utf-8").splitlines() == [
1339
+ "--base",
1340
+ default_branch_tip,
1341
+ ]
1342
+
1343
+
1240
1344
  def test_main_allows_default_branch_file_the_rebase_replayed_onto(
1241
1345
  tmp_path: Path,
1242
1346
  monkeypatch: pytest.MonkeyPatch,