claude-dev-env 8.32.1 → 8.33.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.
@@ -184,3 +184,54 @@ def test_a_node_fix_whose_changed_test_fails_on_head_fails(
184
184
 
185
185
  assert _run_check(repository_root, "fix(bin): sum operands", base_revision) == 1
186
186
  assert "fails on the head" in capsys.readouterr().err
187
+
188
+
189
+ BUGGY_GREETING_TEXT = 'def greet() -> str:\n return "helo"\n'
190
+ FIXED_GREETING_TEXT = 'def greet() -> str:\n return "hello"\n'
191
+ GREETING_TEST_TEXT = (
192
+ "from greet import greet\n\n\ndef test_greet_spells_hello() -> None:\n"
193
+ ' assert greet() == "hello"\n'
194
+ )
195
+ MERGE_REF_FIRST_PARENT = "HEAD^1"
196
+
197
+
198
+ def _merge_ref_after_base_moved(
199
+ tmp_path: Path, all_pull_request_files: dict[str, str]
200
+ ) -> Path:
201
+ repository_root = repository_with_root_pytest_config(tmp_path)
202
+ _commit_files(
203
+ repository_root,
204
+ {"pkg/calc.py": BUGGY_PRODUCTION_TEXT, "other/greet.py": BUGGY_GREETING_TEXT},
205
+ )
206
+ run_git(repository_root, "checkout", "-b", "pull-request")
207
+ _commit_files(repository_root, all_pull_request_files)
208
+ run_git(repository_root, "checkout", "main")
209
+ _commit_files(
210
+ repository_root,
211
+ {"other/greet.py": FIXED_GREETING_TEXT, "other/test_greet.py": GREETING_TEST_TEXT},
212
+ )
213
+ run_git(repository_root, "merge", "--no-ff", "--no-edit", "pull-request")
214
+ return repository_root
215
+
216
+
217
+ def test_a_fix_without_its_own_test_fails_when_the_base_branch_moved_with_a_test(
218
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
219
+ ) -> None:
220
+ repository_root = _merge_ref_after_base_moved(
221
+ tmp_path, {"pkg/calc.py": FIXED_PRODUCTION_TEXT}
222
+ )
223
+
224
+ assert _run_check(repository_root, "fix: sum operands", MERGE_REF_FIRST_PARENT) == 1
225
+ assert "no Python or Node test" in capsys.readouterr().err
226
+
227
+
228
+ def test_a_fix_on_a_moved_base_branch_judges_only_its_own_test(
229
+ tmp_path: Path, capsys: pytest.CaptureFixture[str]
230
+ ) -> None:
231
+ repository_root = _merge_ref_after_base_moved(
232
+ tmp_path,
233
+ {"pkg/calc.py": FIXED_PRODUCTION_TEXT, "pkg/test_calc.py": PROOF_TEST_TEXT},
234
+ )
235
+
236
+ assert _run_check(repository_root, "fix: sum operands", MERGE_REF_FIRST_PARENT) == 0
237
+ assert "fix_pr_test_proof: 1 changed test(s)" in capsys.readouterr().out
@@ -31,7 +31,6 @@ Hosted by ``blocking/bash_post_call_dispatcher.py``, which forwards the
31
31
  from __future__ import annotations
32
32
 
33
33
  import json
34
- import shlex
35
34
  import subprocess
36
35
  import sys
37
36
  from pathlib import Path
@@ -64,9 +63,6 @@ try:
64
63
  GH_PR_CREATE_ACTION,
65
64
  GH_PR_SUBCOMMAND,
66
65
  ALL_GH_PR_VIEW_ARGUMENTS,
67
- ALL_GIT_OPTIONS_WITH_VALUE,
68
- ALL_POWERSHELL_COMMAND_FLAGS,
69
- ALL_POWERSHELL_PROGRAM_NAMES,
70
66
  GH_PR_VIEW_TIMEOUT_SECONDS,
71
67
  GH_PROGRAM_NAME,
72
68
  GIT_PROGRAM_NAME,
@@ -86,9 +82,13 @@ try:
86
82
  )
87
83
  from hooks_constants.pre_tool_use_stdin import read_hook_input_dictionary_from_stdin
88
84
  from hooks_constants.shell_command_segments import (
89
- effective_leading_program,
85
+ command_tokens,
86
+ git_subcommand_tokens,
90
87
  split_into_segments,
91
- token_basename,
88
+ )
89
+ from hooks_constants.shell_command_wrappers import (
90
+ all_wrapped_command_texts,
91
+ segment_program_and_arguments,
92
92
  )
93
93
  except ImportError as import_error:
94
94
  raise ImportError(
@@ -97,47 +97,10 @@ except ImportError as import_error:
97
97
  ) from import_error
98
98
 
99
99
 
100
- def _command_tokens(command: str) -> list[str]:
101
- try:
102
- return shlex.split(command, posix=True)
103
- except ValueError:
104
- return command.split()
105
-
106
-
107
- def _segment_program_and_arguments(all_segment_tokens: list[str]) -> tuple[str, list[str]]:
108
- program_token = effective_leading_program(all_segment_tokens)
109
- if program_token is None:
110
- return "", []
111
- program_index = all_segment_tokens.index(program_token)
112
- return token_basename(program_token), all_segment_tokens[program_index + 1 :]
113
-
114
-
115
- def _git_subcommand(all_arguments: list[str]) -> str | None:
116
- """Return git's subcommand after its global options.
117
-
118
- ``git -C <path> push`` and ``git -c <key=value> push`` carry an option
119
- value before the subcommand; ``--git-dir=<path>`` carries none. The first
120
- token left after those is the subcommand, so ``git stash push`` yields
121
- ``stash`` and ``git log --grep push`` yields ``log``.
122
- """
123
- should_skip_next_token = False
124
- for each_argument in all_arguments:
125
- if should_skip_next_token:
126
- should_skip_next_token = False
127
- continue
128
- if each_argument in ALL_GIT_OPTIONS_WITH_VALUE:
129
- should_skip_next_token = True
130
- continue
131
- if each_argument.startswith("-"):
132
- continue
133
- return each_argument
134
- return None
135
-
136
-
137
100
  def _is_git_push(program_name: str, all_arguments: list[str]) -> bool:
138
- return (
139
- program_name == GIT_PROGRAM_NAME and _git_subcommand(all_arguments) == GIT_PUSH_SUBCOMMAND
140
- )
101
+ return program_name == GIT_PROGRAM_NAME and git_subcommand_tokens(all_arguments)[:1] == [
102
+ GIT_PUSH_SUBCOMMAND
103
+ ]
141
104
 
142
105
 
143
106
  def _is_gh_pr_create(program_name: str, all_arguments: list[str]) -> bool:
@@ -147,25 +110,6 @@ def _is_gh_pr_create(program_name: str, all_arguments: list[str]) -> bool:
147
110
  ]
148
111
 
149
112
 
150
- def _powershell_inner_commands(all_command_tokens: list[str]) -> list[str]:
151
- """Return the script texts every ``pwsh -Command "..."`` wrapper runs.
152
-
153
- Read from the raw shlex tokens, before segment splitting: the quoted
154
- script is one token here, and splitting it on ``;`` first would cut a
155
- ``git add -A; git push`` script in half.
156
- """
157
- all_inner_commands: list[str] = []
158
- has_seen_powershell_program = False
159
- for each_token_index, each_token in enumerate(all_command_tokens[:-1]):
160
- if token_basename(each_token) in ALL_POWERSHELL_PROGRAM_NAMES:
161
- has_seen_powershell_program = True
162
- continue
163
- if has_seen_powershell_program and each_token.lower() in ALL_POWERSHELL_COMMAND_FLAGS:
164
- all_inner_commands.append(all_command_tokens[each_token_index + 1])
165
- has_seen_powershell_program = False
166
- return all_inner_commands
167
-
168
-
169
113
  def command_triggers_reminder(command: str) -> bool:
170
114
  """Return True when any segment of the command is a git push or a gh pr create.
171
115
 
@@ -175,16 +119,13 @@ def command_triggers_reminder(command: str) -> bool:
175
119
  Args:
176
120
  command: The Bash command text the agent ran.
177
121
  """
178
- all_command_tokens = _command_tokens(command)
179
- for each_inner_command in _powershell_inner_commands(all_command_tokens):
180
- if command_triggers_reminder(each_inner_command):
181
- return True
182
- for each_segment in split_into_segments(all_command_tokens):
183
- program_name, all_arguments = _segment_program_and_arguments(each_segment)
184
- if _is_git_push(program_name, all_arguments) or _is_gh_pr_create(
185
- program_name, all_arguments
186
- ):
187
- return True
122
+ for each_command_text in all_wrapped_command_texts(command):
123
+ for each_segment in split_into_segments(command_tokens(each_command_text)):
124
+ program_name, all_arguments = segment_program_and_arguments(each_segment)
125
+ if _is_git_push(program_name, all_arguments) or _is_gh_pr_create(
126
+ program_name, all_arguments
127
+ ):
128
+ return True
188
129
  return False
189
130
 
190
131
 
@@ -95,9 +95,24 @@ def _additional_context(stdout_text: str) -> str:
95
95
  "git add -A && git commit -F m.txt && git push",
96
96
  "git -c push.default=current push",
97
97
  "git --git-dir=C:/repo/.git push",
98
+ "git --git-dir C:/repo/.git push",
99
+ "git --work-tree C:/repo push",
100
+ "git --namespace n push",
98
101
  'pwsh -NoProfile -Command "git push -u origin feat/x"',
99
102
  "pwsh -NoProfile -Command 'gh pr create --draft --fill'",
100
103
  'powershell -Command "git add -A; git push"',
104
+ "pwsh -Command git push origin HEAD",
105
+ "pwsh -Command git push origin HEAD; echo done",
106
+ "sudo git push",
107
+ 'bash -c "git push"',
108
+ "sh -c 'git push'",
109
+ "bash -lc 'gh pr create --draft --fill'",
110
+ "env VAR=x git push",
111
+ "echo origin | xargs git push",
112
+ "git.exe push origin HEAD",
113
+ "env -u HOME git push",
114
+ "timeout --signal KILL 5 git push",
115
+ "echo origin | xargs -n 1 git push",
101
116
  ],
102
117
  )
103
118
  def test_should_wake_on_git_push_and_gh_pr_create(command: str) -> None:
@@ -116,6 +131,10 @@ def test_should_wake_on_git_push_and_gh_pr_create(command: str) -> None:
116
131
  "gh pr ready --undo",
117
132
  "echo git push",
118
133
  'pwsh -NoProfile -Command "git status"',
134
+ "pwsh -Command git status",
135
+ 'bash -c "git status"',
136
+ "sudo cat notes.txt",
137
+ "rg 'git push' docs",
119
138
  "pytest tests/",
120
139
  ],
121
140
  )