claude-dev-env 1.32.0 → 1.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.
@@ -5,17 +5,29 @@ import subprocess
5
5
  import sys
6
6
  from pathlib import Path
7
7
 
8
+ sys.modules.pop("config", None)
9
+ if str(Path(__file__).resolve().parent) not in sys.path:
10
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
11
+
12
+ from config.bugteam_fix_hookspath_constants import (
13
+ ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS,
14
+ ALL_HOME_ENV_VAR_NAMES,
15
+ HOOKS_PATH_SUFFIX,
16
+ PREFLIGHT_NO_PYTEST_FLAG,
17
+ PREFLIGHT_REPO_ROOT_FLAG,
18
+ )
19
+
8
20
 
9
21
  def _expected_hooks_path_suffix() -> str:
10
- return "hooks/git-hooks"
22
+ return HOOKS_PATH_SUFFIX
11
23
 
12
24
 
13
- def _canonical_hooks_directory_components() -> tuple[str, ...]:
14
- return (".claude", "hooks", "git-hooks")
25
+ def _canonical_hooks_directory_components() -> tuple[str, str, str]:
26
+ return ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS
15
27
 
16
28
 
17
- def _home_env_var_names() -> tuple[str, ...]:
18
- return ("HOME", "USERPROFILE")
29
+ def _home_env_var_names() -> tuple[str, str]:
30
+ return ALL_HOME_ENV_VAR_NAMES
19
31
 
20
32
 
21
33
  def resolve_canonical_hooks_directory(
@@ -82,7 +94,7 @@ def read_global_core_hooks_path(
82
94
  def unset_local_core_hooks_path(
83
95
  repository_root: Path,
84
96
  environment_overrides: dict[str, str] | None,
85
- ) -> None:
97
+ ) -> int:
86
98
  git_command = [
87
99
  "git",
88
100
  "-C",
@@ -92,13 +104,14 @@ def unset_local_core_hooks_path(
92
104
  "--unset-all",
93
105
  "core.hooksPath",
94
106
  ]
95
- subprocess.run(
107
+ completed_process = subprocess.run(
96
108
  git_command,
97
109
  capture_output=True,
98
110
  text=True,
99
111
  check=False,
100
112
  env=environment_overrides,
101
113
  )
114
+ return completed_process.returncode
102
115
 
103
116
 
104
117
  def set_global_core_hooks_path(
@@ -144,8 +157,8 @@ def rerun_preflight(
144
157
  rerun_command = [
145
158
  sys.executable,
146
159
  str(preflight_script_path),
147
- "--no-pytest",
148
- "--repo-root",
160
+ PREFLIGHT_NO_PYTEST_FLAG,
161
+ PREFLIGHT_REPO_ROOT_FLAG,
149
162
  str(repository_root),
150
163
  ]
151
164
  completed_process = subprocess.run(
@@ -156,7 +169,7 @@ def rerun_preflight(
156
169
  return completed_process.returncode
157
170
 
158
171
 
159
- def parse_arguments(argv: list[str]) -> argparse.Namespace:
172
+ def parse_arguments(argv: list[str] | None) -> argparse.Namespace:
160
173
  parser = argparse.ArgumentParser(
161
174
  description=(
162
175
  "Auto-fix core.hooksPath when bugteam preflight detects a stale override. "
@@ -178,7 +191,7 @@ def main(
178
191
  *,
179
192
  environment_overrides: dict[str, str] | None = None,
180
193
  ) -> int:
181
- arguments = parse_arguments(sys.argv[1:] if argv is None else argv)
194
+ arguments = parse_arguments(argv)
182
195
  start_directory = Path.cwd()
183
196
  repository_root = (
184
197
  arguments.repo_root.resolve()
@@ -206,7 +219,16 @@ def main(
206
219
  for each_value in local_hooks_path_values
207
220
  )
208
221
  if has_non_canonical_local_override:
209
- unset_local_core_hooks_path(repository_root, environment_overrides)
222
+ unset_local_returncode = unset_local_core_hooks_path(
223
+ repository_root, environment_overrides
224
+ )
225
+ if unset_local_returncode != 0:
226
+ print(
227
+ "bugteam_fix_hookspath: failed to unset local core.hooksPath on "
228
+ f"{repository_root} (git exit {unset_local_returncode}).",
229
+ file=sys.stderr,
230
+ )
231
+ return 1
210
232
  print(
211
233
  "bugteam_fix_hookspath: removed stale local core.hooksPath override on "
212
234
  f"{repository_root}",
File without changes
@@ -0,0 +1,17 @@
1
+ """Configuration constants for bugteam_fix_hookspath auto-remediation script."""
2
+
3
+ from __future__ import annotations
4
+
5
+ HOOKS_PATH_SUFFIX: str = "hooks/git-hooks"
6
+
7
+ ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS: tuple[str, str, str] = (
8
+ ".claude",
9
+ "hooks",
10
+ "git-hooks",
11
+ )
12
+
13
+ ALL_HOME_ENV_VAR_NAMES: tuple[str, str] = ("HOME", "USERPROFILE")
14
+
15
+ PREFLIGHT_NO_PYTEST_FLAG: str = "--no-pytest"
16
+
17
+ PREFLIGHT_REPO_ROOT_FLAG: str = "--repo-root"
@@ -11,16 +11,23 @@ The default failure mode is shipping a rebase that compiled but doesn't run. Thi
11
11
 
12
12
  ## When to rebase vs. merge
13
13
 
14
+ > **Decision thresholds**
15
+ >
16
+ > | Threshold | Value |
17
+ > |---|---|
18
+ > | Maximum commits for solo rebase | 5 |
19
+ > | Divergence age that triggers merge-instead | 2 weeks |
20
+
14
21
  **Default to rebase** when:
15
22
 
16
23
  - Solo branch (you are the only author of the commits being rebased).
17
- - 1–5 commits ahead of base.
24
+ - 1–5 commits ahead of base (see Decision thresholds above).
18
25
  - Stacked PR whose base just merged via squash.
19
26
 
20
27
  **Default to merge** when:
21
28
 
22
29
  - Branch has multiple authors (force-push would clobber their state).
23
- - More than ~5 commits or more than ~2 weeks of divergence (rebase complexity grows non-linearly).
30
+ - More than 5 commits or more than 2 weeks of divergence (see Decision thresholds above; rebase complexity grows non-linearly).
24
31
  - The user said "merge", not "rebase".
25
32
  - Open PR with approving reviews already on the current SHA (force-push invalidates them).
26
33
 
@@ -74,10 +81,10 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
74
81
  6. **Audit auto-merged files.** Files that git merged without conflict markers are not automatically correct. After each commit applies, run:
75
82
 
76
83
  ```
77
- git diff --name-only --diff-filter=M HEAD@{1}
84
+ git diff --name-only --diff-filter=M ORIG_HEAD
78
85
  ```
79
86
 
80
- For each modified file with no conflict markers, eyeball the changes — auto-merge can produce duplicate blocks (when both sides added similar content) or silently drop content (when both sides removed adjacent lines). Pay extra attention to `config/`, `constants.*`, and `__init__.py` files where additions often sit near each other.
87
+ Use `ORIG_HEAD`, which git sets at rebase start; the reflog index `HEAD@{1}` shifts as each rebase step runs and is unreliable mid-rebase. For each modified file with no conflict markers, eyeball the changes — auto-merge can produce duplicate blocks (when both sides added similar content) or silently drop content (when both sides removed adjacent lines). Pay extra attention to `config/`, `constants.*`, and `__init__.py` files where additions often sit near each other.
81
88
 
82
89
  7. **At every conflict, take both sides' intent seriously.** Read both, then decide based on the post-rebase logical state. Do not reflex-pick HEAD or `origin/main`. Document the resolution reasoning in the commit message if it is non-obvious.
83
90
 
@@ -88,10 +95,10 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
88
95
  8. **Real import check.** For Python:
89
96
 
90
97
  ```
91
- python -c "import <every_top_level_module_in_changed_packages>"
98
+ python -m compileall -q <package>
92
99
  ```
93
100
 
94
- This catches the most common rebase failure: an import that survived the rebase pointing at a name the rebased commits removed or renamed.
101
+ Follow immediately with the test-collection step below. `compileall` catches import-time failures across every module file; combined with `--collect-only` it surfaces `NameError`, `AttributeError`, and `ImportError` cases that a syntax-only check misses.
95
102
 
96
103
  9. **Test collection.** `pytest --collect-only -q` on the changed packages catches NameError, AttributeError, and ImportError surfaces beyond plain imports.
97
104
 
@@ -102,7 +109,7 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
102
109
  - **Preferred:** `mcp__serena__find_referencing_symbols` (symbol-aware; ignores false matches in comments and string literals).
103
110
  - **Fallback:** `mcp__zoekt__search` for cross-repo or large trees.
104
111
  - **Then:** the `Grep` tool (e.g., `Grep(pattern="<symbol>", type="py")`) for fast in-repo scans.
105
- - **Last resort:** `grep -rn "<symbol>" --include='*.py' --include='*.ts' --include='*.json' .`
112
+ - **Last resort:** `grep -rn "<symbol>" .` (let ripgrep defaults and `.gitignore` handle scoping)
106
113
 
107
114
  Any reference outside the rebased commits' own changes is a stale reference. Either update it (with user authorization) or surface it and refuse to push.
108
115
 
@@ -117,7 +124,7 @@ When in doubt, ask. Both work; the choice affects history shape, not correctness
117
124
  - Ask for explicit authorization.
118
125
  - If denied: leave the rebase result locally, report merge-instead as the alternative, stop.
119
126
 
120
- 14. **Refuse to force-push** `main`, `master`, `release/*`, `production`, or any branch with more than one author in `git log --format='%ae' origin/<branch> | sort -u`. Surface the refusal; do not ask for authorization on these.
127
+ 14. **Refuse to force-push** `main`, `master`, `release/*`, `production`, or any branch with more than one unique author. Count unique authors in a cross-platform way: `git log --format='%ae' origin/<branch> | python -c "import sys; print(len(set(sys.stdin)))"`. This form works on both Windows (PowerShell) and Unix without shell pipeline extensions. Surface the refusal; do not ask for authorization on these.
121
128
 
122
129
  15. **Always `--force-with-lease=<branch>:<sha>`**, never bare `--force`. Pin the lease to the SHA you started from so concurrent pushes are detected as a lease mismatch instead of clobbered.
123
130