claude-dev-env 1.93.0 → 1.94.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 (220) hide show
  1. package/_shared/pr-loop/CLAUDE.md +1 -0
  2. package/_shared/pr-loop/audit-contract.md +1 -1
  3. package/_shared/pr-loop/gh-payloads.md +3 -3
  4. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  5. package/_shared/pr-loop/scripts/README.md +2 -2
  6. package/_shared/pr-loop/scripts/code_rules_gate.py +127 -13
  7. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  8. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +4 -0
  9. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +146 -69
  10. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  11. package/_shared/pr-loop/state-schema.md +3 -22
  12. package/bin/CLAUDE.md +1 -1
  13. package/bin/install.mjs +2 -7
  14. package/bin/install.test.mjs +10 -4
  15. package/hooks/blocking/CLAUDE.md +2 -0
  16. package/hooks/blocking/pii_commit_command.py +398 -0
  17. package/hooks/blocking/pii_payload_scan.py +133 -0
  18. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  19. package/hooks/blocking/pii_scanner.py +26 -1
  20. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +2 -2
  21. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  22. package/hooks/blocking/test_pii_scanner.py +27 -2
  23. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  24. package/hooks/hooks_constants/CLAUDE.md +2 -2
  25. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  26. package/hooks/hooks_constants/local_identity.py +65 -15
  27. package/hooks/hooks_constants/pii_prevention_constants.py +20 -8
  28. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  29. package/hooks/hooks_constants/test_local_identity.py +68 -0
  30. package/package.json +1 -1
  31. package/scripts/CLAUDE.md +10 -1
  32. package/scripts/Get-SessionAccount.ps1 +210 -0
  33. package/scripts/tests/CLAUDE.md +15 -1
  34. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  35. package/skills/CLAUDE.md +3 -25
  36. package/skills/anthropic-plan/SKILL.md +1 -13
  37. package/skills/autoconverge/CLAUDE.md +5 -2
  38. package/skills/autoconverge/SKILL.md +112 -387
  39. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  40. package/skills/autoconverge/reference/closing-report.md +9 -2
  41. package/skills/autoconverge/reference/convergence.md +26 -22
  42. package/skills/autoconverge/reference/headless-safety.md +44 -0
  43. package/skills/autoconverge/reference/multi-pr.md +88 -0
  44. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  45. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  46. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  47. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  48. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  49. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  50. package/skills/autoconverge/workflow/converge.mjs +284 -94
  51. package/skills/bugteam/CLAUDE.md +1 -1
  52. package/skills/bugteam/CONSTRAINTS.md +3 -3
  53. package/skills/bugteam/PROMPTS.md +8 -19
  54. package/skills/bugteam/SKILL.md +13 -14
  55. package/skills/bugteam/reference/CLAUDE.md +1 -1
  56. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  57. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  58. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  59. package/skills/bugteam/reference/team-setup.md +11 -11
  60. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  61. package/skills/bugteam/scripts/README.md +20 -8
  62. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  63. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  64. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  65. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  66. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  67. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  68. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  69. package/skills/copilot-finding-triage/SKILL.md +80 -28
  70. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  71. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  72. package/skills/everything-search/SKILL.md +1 -2
  73. package/skills/grokify/SKILL.md +58 -0
  74. package/skills/grokify/templates/handoff-template.md +92 -0
  75. package/skills/orchestrator/SKILL.md +12 -8
  76. package/skills/orchestrator-refresh/SKILL.md +3 -3
  77. package/skills/pr-converge/SKILL.md +132 -233
  78. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  79. package/skills/pr-converge/reference/examples.md +37 -25
  80. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  81. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  82. package/skills/pr-converge/reference/per-tick.md +64 -41
  83. package/skills/pr-converge/reference/state-schema.md +27 -2
  84. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  85. package/skills/pr-converge/scripts/README.md +4 -3
  86. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  87. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  88. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  89. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  90. package/skills/pr-converge/scripts/conftest.py +8 -0
  91. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  92. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  93. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  94. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  95. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  96. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  97. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  98. package/skills/privacy-hygiene/SKILL.md +8 -7
  99. package/skills/reviewer-gates/SKILL.md +26 -8
  100. package/skills/session-log/SKILL.md +11 -12
  101. package/skills/session-tidy/SKILL.md +40 -30
  102. package/skills/skill-builder/CLAUDE.md +13 -10
  103. package/skills/skill-builder/SKILL.md +31 -31
  104. package/skills/skill-builder/references/CLAUDE.md +10 -6
  105. package/skills/skill-builder/references/delegation-map.md +13 -4
  106. package/skills/skill-builder/references/description-field.md +113 -0
  107. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  108. package/skills/skill-builder/references/skill-modularity.md +113 -0
  109. package/skills/skill-builder/references/skill-types.md +4 -0
  110. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  111. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  112. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  113. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  114. package/skills/skill-builder/workflows/new-skill.md +34 -10
  115. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  116. package/skills/usage-pause/SKILL.md +10 -14
  117. package/skills/bdd-protocol/CLAUDE.md +0 -26
  118. package/skills/bdd-protocol/SKILL.md +0 -30
  119. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  120. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  121. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  122. package/skills/bg-agent/CLAUDE.md +0 -17
  123. package/skills/bg-agent/SKILL.md +0 -69
  124. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  125. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  126. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  127. package/skills/caveman/CLAUDE.md +0 -15
  128. package/skills/caveman/SKILL.md +0 -38
  129. package/skills/code/CLAUDE.md +0 -17
  130. package/skills/code/SKILL.md +0 -178
  131. package/skills/deep-research/CLAUDE.md +0 -17
  132. package/skills/deep-research/SKILL.md +0 -80
  133. package/skills/findbugs/CLAUDE.md +0 -20
  134. package/skills/findbugs/SKILL.md +0 -241
  135. package/skills/fixbugs/CLAUDE.md +0 -19
  136. package/skills/fixbugs/SKILL.md +0 -142
  137. package/skills/gh-paginate/CLAUDE.md +0 -18
  138. package/skills/gh-paginate/SKILL.md +0 -84
  139. package/skills/gotcha/CLAUDE.md +0 -33
  140. package/skills/gotcha/SKILL.md +0 -73
  141. package/skills/implement/CLAUDE.md +0 -27
  142. package/skills/implement/SKILL.md +0 -66
  143. package/skills/implement/scripts/CLAUDE.md +0 -22
  144. package/skills/implement/scripts/append_note.py +0 -133
  145. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  146. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  147. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  148. package/skills/implement/scripts/test_append_note.py +0 -191
  149. package/skills/log-audit/CLAUDE.md +0 -20
  150. package/skills/log-audit/SKILL.md +0 -66
  151. package/skills/log-audit/reference/CLAUDE.md +0 -9
  152. package/skills/log-audit/reference/charter.md +0 -52
  153. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  154. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  155. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  156. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  157. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  158. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  159. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  160. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  161. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  162. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  163. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  164. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  165. package/skills/logifix/CLAUDE.md +0 -36
  166. package/skills/logifix/SKILL.md +0 -69
  167. package/skills/logifix/scripts/CLAUDE.md +0 -16
  168. package/skills/logifix/scripts/logifix.ps1 +0 -205
  169. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  170. package/skills/monitor-open-prs/SKILL.md +0 -88
  171. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  172. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  173. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  174. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  175. package/skills/post-audit-findings/SKILL.md +0 -80
  176. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  177. package/skills/pr-consistency-audit/SKILL.md +0 -116
  178. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  179. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  180. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  181. package/skills/pr-fix-protocol/SKILL.md +0 -65
  182. package/skills/pr-review-responder/CLAUDE.md +0 -35
  183. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  184. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  185. package/skills/pr-review-responder/README.md +0 -168
  186. package/skills/pr-review-responder/SKILL.md +0 -203
  187. package/skills/pr-review-responder/TESTING.md +0 -350
  188. package/skills/pr-scope-resolve/SKILL.md +0 -45
  189. package/skills/pre-compact/CLAUDE.md +0 -24
  190. package/skills/pre-compact/SKILL.md +0 -134
  191. package/skills/qbug/CLAUDE.md +0 -40
  192. package/skills/qbug/SKILL.md +0 -387
  193. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  194. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  195. package/skills/refine/CLAUDE.md +0 -44
  196. package/skills/refine/SKILL.md +0 -262
  197. package/skills/refine/templates/CLAUDE.md +0 -17
  198. package/skills/refine/templates/implementation-notes-template.html +0 -56
  199. package/skills/refine/templates/plan-template.md +0 -60
  200. package/skills/research-mode/CLAUDE.md +0 -35
  201. package/skills/research-mode/SKILL.md +0 -53
  202. package/skills/structure-prompt/CLAUDE.md +0 -42
  203. package/skills/structure-prompt/SKILL.md +0 -44
  204. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  205. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  206. package/skills/structure-prompt/reference/block-classification.md +0 -27
  207. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  208. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  209. package/skills/structure-prompt/reference/cleanup.md +0 -33
  210. package/skills/structure-prompt/reference/constraints.md +0 -33
  211. package/skills/structure-prompt/reference/directives.md +0 -37
  212. package/skills/structure-prompt/reference/examples.md +0 -72
  213. package/skills/structure-prompt/reference/instantiation.md +0 -51
  214. package/skills/structure-prompt/reference/output-contract.md +0 -72
  215. package/skills/structure-prompt/reference/per-category.md +0 -23
  216. package/skills/structure-prompt/reference/persona.md +0 -38
  217. package/skills/structure-prompt/reference/research.md +0 -33
  218. package/skills/structure-prompt/reference/structure.md +0 -28
  219. package/skills/verified-build/CLAUDE.md +0 -33
  220. package/skills/verified-build/SKILL.md +0 -33
@@ -1,358 +1,41 @@
1
- """Tests for bugteam_fix_hookspath auto-remediation.
1
+ """Smoke tests for the skill-path fix_hookspath wrap.
2
2
 
3
- Covers:
4
- - removes a local-scope core.hooksPath override and re-runs preflight
5
- - sets global core.hooksPath when missing
6
- - idempotent: second invocation produces the same final state with no errors
7
- - no-op when no override exists and global is already canonical
8
- - exits non-zero with a clear message when canonical hooks dir is missing
3
+ Behavioral coverage lives at
4
+ ``_shared/pr-loop/scripts/tests/test_fix_hookspath.py``.
9
5
  """
10
6
 
11
7
  from __future__ import annotations
12
8
 
13
9
  import importlib.util
14
- import os
15
- import subprocess
16
- import sys
17
10
  from pathlib import Path
18
11
  from types import ModuleType
19
- from unittest.mock import patch
20
12
 
21
- import pytest
22
13
 
23
-
24
- def _load_fix_module() -> ModuleType:
14
+ def _load_wrap_module() -> ModuleType:
25
15
  module_path = Path(__file__).parent / "bugteam_fix_hookspath.py"
26
- spec = importlib.util.spec_from_file_location("bugteam_fix_hookspath", module_path)
27
- assert spec is not None
28
- assert spec.loader is not None
29
- module = importlib.util.module_from_spec(spec)
30
- spec.loader.exec_module(module)
31
- return module
32
-
33
-
34
- bugteam_fix_hookspath = _load_fix_module()
35
-
36
-
37
- def _make_isolated_git_environment(home_directory: Path) -> dict[str, str]:
38
- """Build an env dict that pins git's HOME and XDG paths into a tmp directory.
39
-
40
- Without this, real `git config --global` reads/writes hit the developer's
41
- actual ~/.gitconfig which would corrupt the host machine and make tests
42
- depend on global state. Pointing HOME, USERPROFILE, and XDG_CONFIG_HOME
43
- at a temp directory isolates the test fully on every supported git
44
- version. GIT_CONFIG_GLOBAL would tighten the binding but requires
45
- git >= 2.32 (August 2021); HOME/USERPROFILE already isolate on older git.
46
- """
47
- isolated_environment = os.environ.copy()
48
- isolated_environment["HOME"] = str(home_directory)
49
- isolated_environment["USERPROFILE"] = str(home_directory)
50
- isolated_environment["XDG_CONFIG_HOME"] = str(home_directory / ".config")
51
- isolated_environment["GIT_CONFIG_NOSYSTEM"] = "1"
52
- return isolated_environment
53
-
54
-
55
- def _initialize_repository(repository_path: Path, environment: dict[str, str]) -> None:
56
- repository_path.mkdir(parents=True, exist_ok=True)
57
- subprocess.run(
58
- ["git", "init", "--quiet", str(repository_path)],
59
- check=True,
60
- env=environment,
61
- )
62
-
63
-
64
- def _set_local_hooks_path(
65
- repository_path: Path,
66
- hooks_path_value: str,
67
- environment: dict[str, str],
68
- ) -> None:
69
- subprocess.run(
70
- [
71
- "git",
72
- "-C",
73
- str(repository_path),
74
- "config",
75
- "--local",
76
- "core.hooksPath",
77
- hooks_path_value,
78
- ],
79
- check=True,
80
- env=environment,
81
- )
82
-
83
-
84
- def _set_global_hooks_path(hooks_path_value: str, environment: dict[str, str]) -> None:
85
- subprocess.run(
86
- ["git", "config", "--global", "core.hooksPath", hooks_path_value],
87
- check=True,
88
- env=environment,
89
- )
90
-
91
-
92
- def _read_local_hooks_path(repository_path: Path, environment: dict[str, str]) -> str:
93
- completed_process = subprocess.run(
94
- [
95
- "git",
96
- "-C",
97
- str(repository_path),
98
- "config",
99
- "--local",
100
- "--get",
101
- "core.hooksPath",
102
- ],
103
- capture_output=True,
104
- text=True,
105
- check=False,
106
- env=environment,
107
- )
108
- return completed_process.stdout.strip()
109
-
110
-
111
- def _read_global_hooks_path(environment: dict[str, str]) -> str:
112
- completed_process = subprocess.run(
113
- ["git", "config", "--global", "--get", "core.hooksPath"],
114
- capture_output=True,
115
- text=True,
116
- check=False,
117
- env=environment,
118
- )
119
- return completed_process.stdout.strip()
120
-
121
-
122
- def _create_canonical_hooks_directory(home_directory: Path) -> Path:
123
- canonical_hooks_directory = home_directory / ".claude" / "hooks" / "git-hooks"
124
- canonical_hooks_directory.mkdir(parents=True)
125
- return canonical_hooks_directory
126
-
127
-
128
- def test_should_remove_local_override_and_pass_preflight(tmp_path: Path) -> None:
129
- home_directory = tmp_path / "home"
130
- home_directory.mkdir()
131
- environment = _make_isolated_git_environment(home_directory)
132
- canonical_hooks_directory = _create_canonical_hooks_directory(home_directory)
133
- _set_global_hooks_path(str(canonical_hooks_directory), environment)
134
- repository_path = tmp_path / "synthetic-repo"
135
- _initialize_repository(repository_path, environment)
136
- stale_local_value = str(repository_path / ".git" / "hooks")
137
- _set_local_hooks_path(repository_path, stale_local_value, environment)
138
-
139
- exit_code = bugteam_fix_hookspath.main(
140
- ["--repo-root", str(repository_path)],
141
- all_environment_overrides=environment,
142
- )
143
-
144
- assert exit_code == 0, (
145
- "fix script must succeed when canonical global hooks dir exists"
146
- )
147
- assert _read_local_hooks_path(repository_path, environment) == "", (
148
- "local core.hooksPath override must be removed"
149
- )
150
-
151
-
152
- def test_should_set_global_hooks_path_when_missing(tmp_path: Path) -> None:
153
- home_directory = tmp_path / "home"
154
- home_directory.mkdir()
155
- environment = _make_isolated_git_environment(home_directory)
156
- canonical_hooks_directory = _create_canonical_hooks_directory(home_directory)
157
- repository_path = tmp_path / "synthetic-repo"
158
- _initialize_repository(repository_path, environment)
159
- stale_local_value = str(repository_path / ".git" / "hooks")
160
- _set_local_hooks_path(repository_path, stale_local_value, environment)
161
-
162
- exit_code = bugteam_fix_hookspath.main(
163
- ["--repo-root", str(repository_path)],
164
- all_environment_overrides=environment,
165
- )
166
-
167
- assert exit_code == 0
168
- global_value_after_fix = _read_global_hooks_path(environment)
169
- assert (
170
- global_value_after_fix.replace("\\", "/")
171
- .rstrip("/")
172
- .endswith("hooks/git-hooks")
173
- ), (
174
- "fix script must set canonical global core.hooksPath when missing; "
175
- f"got '{global_value_after_fix}'"
176
- )
177
-
178
-
179
- def test_should_be_idempotent(tmp_path: Path) -> None:
180
- home_directory = tmp_path / "home"
181
- home_directory.mkdir()
182
- environment = _make_isolated_git_environment(home_directory)
183
- canonical_hooks_directory = _create_canonical_hooks_directory(home_directory)
184
- _set_global_hooks_path(str(canonical_hooks_directory), environment)
185
- repository_path = tmp_path / "synthetic-repo"
186
- _initialize_repository(repository_path, environment)
187
- stale_local_value = str(repository_path / ".git" / "hooks")
188
- _set_local_hooks_path(repository_path, stale_local_value, environment)
189
-
190
- first_exit_code = bugteam_fix_hookspath.main(
191
- ["--repo-root", str(repository_path)],
192
- all_environment_overrides=environment,
193
- )
194
- second_exit_code = bugteam_fix_hookspath.main(
195
- ["--repo-root", str(repository_path)],
196
- all_environment_overrides=environment,
197
- )
198
-
199
- assert first_exit_code == 0
200
- assert second_exit_code == 0, "second invocation must succeed without errors"
201
- assert _read_local_hooks_path(repository_path, environment) == ""
202
-
203
-
204
- def test_should_no_op_when_already_clean(tmp_path: Path) -> None:
205
- home_directory = tmp_path / "home"
206
- home_directory.mkdir()
207
- environment = _make_isolated_git_environment(home_directory)
208
- canonical_hooks_directory = _create_canonical_hooks_directory(home_directory)
209
- _set_global_hooks_path(str(canonical_hooks_directory), environment)
210
- repository_path = tmp_path / "synthetic-repo"
211
- _initialize_repository(repository_path, environment)
212
-
213
- exit_code = bugteam_fix_hookspath.main(
214
- ["--repo-root", str(repository_path)],
215
- all_environment_overrides=environment,
216
- )
217
-
218
- assert exit_code == 0
219
- assert _read_local_hooks_path(repository_path, environment) == ""
220
- assert (
221
- _read_global_hooks_path(environment)
222
- .replace("\\", "/")
223
- .rstrip("/")
224
- .endswith("hooks/git-hooks")
225
- )
226
-
227
-
228
- def test_should_exit_nonzero_when_canonical_hooks_directory_missing(
229
- tmp_path: Path,
230
- capsys: pytest.CaptureFixture[str],
231
- ) -> None:
232
- home_directory = tmp_path / "home"
233
- home_directory.mkdir()
234
- environment = _make_isolated_git_environment(home_directory)
235
- repository_path = tmp_path / "synthetic-repo"
236
- _initialize_repository(repository_path, environment)
237
- stale_local_value = str(repository_path / ".git" / "hooks")
238
- _set_local_hooks_path(repository_path, stale_local_value, environment)
239
-
240
- exit_code = bugteam_fix_hookspath.main(
241
- ["--repo-root", str(repository_path)],
242
- all_environment_overrides=environment,
243
- )
244
-
245
- assert exit_code != 0, (
246
- "fix script must fail clearly when ~/.claude/hooks/git-hooks does not exist "
247
- "so the user knows to run `npx claude-dev-env .`"
248
- )
249
- captured_streams = capsys.readouterr()
250
- assert "hooks/git-hooks" in captured_streams.err.replace("\\", "/")
251
-
252
-
253
- def test_should_handle_paths_with_spaces(tmp_path: Path) -> None:
254
- home_directory = tmp_path / "home with space"
255
- home_directory.mkdir()
256
- environment = _make_isolated_git_environment(home_directory)
257
- canonical_hooks_directory = _create_canonical_hooks_directory(home_directory)
258
- _set_global_hooks_path(str(canonical_hooks_directory), environment)
259
- repository_path = tmp_path / "repo with space"
260
- _initialize_repository(repository_path, environment)
261
- stale_local_value = str(repository_path / ".git" / "hooks")
262
- _set_local_hooks_path(repository_path, stale_local_value, environment)
263
-
264
- exit_code = bugteam_fix_hookspath.main(
265
- ["--repo-root", str(repository_path)],
266
- all_environment_overrides=environment,
267
- )
268
-
269
- assert exit_code == 0
270
- assert _read_local_hooks_path(repository_path, environment) == ""
271
-
272
-
273
- def test_list_local_core_hooks_path_values_raises_on_unexpected_git_failure(
274
- tmp_path: Path,
275
- ) -> None:
276
- """A non-empty stderr from git config must propagate as an error.
277
-
278
- Regression for loop1-5: returning [] on every non-zero git exit collapses
279
- "key unset" with "git failed for some other reason" — the caller then
280
- skips the unset call, leaving a stale local override in place.
281
- """
282
- repository_path = tmp_path / "repo"
283
- repository_path.mkdir()
284
-
285
- def fake_run(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess:
286
- return subprocess.CompletedProcess(
287
- args=[],
288
- returncode=128,
289
- stdout="",
290
- stderr="fatal: unable to read config file: permission denied\n",
291
- )
292
-
293
- with patch.object(subprocess, "run", fake_run):
294
- with pytest.raises(RuntimeError):
295
- bugteam_fix_hookspath.list_local_core_hooks_path_values(
296
- repository_path,
297
- None,
298
- )
299
-
300
-
301
- def test_read_global_core_hooks_path_raises_on_unexpected_git_failure() -> None:
302
- """A non-empty stderr from git config must propagate as an error.
303
-
304
- Regression for loop1-6: returning "" on every non-zero git exit conflates
305
- "global hooksPath unset" with "git failed for some other reason" — the
306
- caller then overwrites global git config based on a non-truthful read.
307
- """
308
-
309
- def fake_run(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess:
310
- return subprocess.CompletedProcess(
311
- args=[],
312
- returncode=128,
313
- stdout="",
314
- stderr="fatal: bad config line\n",
315
- )
316
-
317
- with patch.object(subprocess, "run", fake_run):
318
- with pytest.raises(RuntimeError):
319
- bugteam_fix_hookspath.read_global_core_hooks_path(None)
320
-
321
-
322
- def test_list_local_core_hooks_path_values_returns_empty_when_key_unset(
323
- tmp_path: Path,
324
- ) -> None:
325
- """Genuine key-unset (exit 1 + empty stderr) must continue to return []."""
326
- repository_path = tmp_path / "repo"
327
- repository_path.mkdir()
328
-
329
- def fake_run(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess:
330
- return subprocess.CompletedProcess(
331
- args=[],
332
- returncode=1,
333
- stdout="",
334
- stderr="",
335
- )
336
-
337
- with patch.object(subprocess, "run", fake_run):
338
- result = bugteam_fix_hookspath.list_local_core_hooks_path_values(
339
- repository_path,
340
- None,
341
- )
342
- assert result == []
343
-
344
-
345
- def test_read_global_core_hooks_path_returns_empty_when_key_unset() -> None:
346
- """Genuine key-unset (exit 1 + empty stderr) must continue to return ''."""
347
-
348
- def fake_run(*_args: object, **_kwargs: object) -> subprocess.CompletedProcess:
349
- return subprocess.CompletedProcess(
350
- args=[],
351
- returncode=1,
352
- stdout="",
353
- stderr="",
354
- )
355
-
356
- with patch.object(subprocess, "run", fake_run):
357
- result = bugteam_fix_hookspath.read_global_core_hooks_path(None)
358
- assert result == ""
16
+ specification = importlib.util.spec_from_file_location(
17
+ "bugteam_fix_hookspath",
18
+ module_path,
19
+ )
20
+ assert specification is not None
21
+ assert specification.loader is not None
22
+ wrap_module = importlib.util.module_from_spec(specification)
23
+ specification.loader.exec_module(wrap_module)
24
+ return wrap_module
25
+
26
+
27
+ def test_wrap_main_delegates_to_shared_module() -> None:
28
+ wrap_module = _load_wrap_module()
29
+ assert wrap_module.main.__module__ == "fix_hookspath"
30
+ shared_filename = Path(wrap_module.main.__code__.co_filename).name
31
+ assert shared_filename == "fix_hookspath.py"
32
+
33
+
34
+ def test_wrap_main_help_exits_cleanly() -> None:
35
+ wrap_module = _load_wrap_module()
36
+ try:
37
+ wrap_module.main(["--help"], None)
38
+ except SystemExit as exit_signal:
39
+ assert exit_signal.code in (0, None)
40
+ return
41
+ raise AssertionError("main(['--help'], None) must raise SystemExit")