claude-dev-env 1.93.1 → 1.95.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 (262) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +35 -27
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -2
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +23 -15
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +81 -17
  6. package/_shared/pr-loop/CLAUDE.md +1 -0
  7. package/_shared/pr-loop/audit-contract.md +1 -1
  8. package/_shared/pr-loop/gh-payloads.md +3 -3
  9. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  10. package/_shared/pr-loop/scripts/README.md +2 -2
  11. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -69
  13. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  14. package/_shared/pr-loop/state-schema.md +3 -22
  15. package/bin/CLAUDE.md +11 -2
  16. package/bin/ever-shipped-skills.mjs +70 -0
  17. package/bin/install.mjs +138 -13
  18. package/bin/install.prune.test.mjs +457 -0
  19. package/bin/install.test.mjs +10 -4
  20. package/docs/CODE_RULES.md +1 -1
  21. package/hooks/blocking/CLAUDE.md +2 -0
  22. package/hooks/blocking/code_rules_enforcer.py +4 -0
  23. package/hooks/blocking/code_rules_shared.py +82 -0
  24. package/hooks/blocking/code_rules_test_layout.py +9 -3
  25. package/hooks/blocking/pii_commit_command.py +398 -0
  26. package/hooks/blocking/pii_payload_scan.py +133 -0
  27. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  28. package/hooks/blocking/plain_language_blocker.py +138 -4
  29. package/hooks/blocking/sensitive_file_protector.py +114 -48
  30. package/hooks/blocking/tdd_enforcer.py +9 -2
  31. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  32. package/hooks/blocking/test_code_rules_shared.py +181 -0
  33. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  34. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  35. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  36. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  38. package/hooks/hooks_constants/CLAUDE.md +4 -2
  39. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  40. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  41. package/hooks/hooks_constants/local_identity.py +65 -15
  42. package/hooks/hooks_constants/pii_prevention_constants.py +16 -0
  43. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  44. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  45. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  46. package/hooks/hooks_constants/test_local_identity.py +68 -0
  47. package/hooks/pyproject.toml +75 -4
  48. package/hooks/validators/CLAUDE.md +1 -1
  49. package/hooks/validators/README.md +2 -0
  50. package/hooks/validators/python_style_checks.py +114 -136
  51. package/hooks/validators/python_style_helpers.py +95 -0
  52. package/hooks/validators/test_python_style_checks.py +0 -164
  53. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  54. package/hooks/validators/test_python_style_fixes.py +251 -0
  55. package/hooks/validators/test_python_style_helpers.py +125 -0
  56. package/package.json +1 -1
  57. package/rules/CLAUDE.md +1 -0
  58. package/rules/anti-corollary-tests.md +69 -0
  59. package/rules/bdd.md +1 -3
  60. package/rules/code-reviews.md +1 -1
  61. package/rules/gh-paginate.md +1 -1
  62. package/rules/plain-language.md +2 -0
  63. package/scripts/CLAUDE.md +10 -1
  64. package/scripts/Get-SessionAccount.ps1 +210 -0
  65. package/scripts/tests/CLAUDE.md +15 -1
  66. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  67. package/skills/CLAUDE.md +6 -27
  68. package/skills/anthropic-plan/SKILL.md +1 -13
  69. package/skills/autoconverge/CLAUDE.md +5 -2
  70. package/skills/autoconverge/SKILL.md +112 -387
  71. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  72. package/skills/autoconverge/reference/closing-report.md +9 -2
  73. package/skills/autoconverge/reference/convergence.md +26 -22
  74. package/skills/autoconverge/reference/headless-safety.md +44 -0
  75. package/skills/autoconverge/reference/multi-pr.md +88 -0
  76. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  77. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  78. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  79. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  80. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  81. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  82. package/skills/autoconverge/workflow/converge.mjs +286 -96
  83. package/skills/bugteam/CLAUDE.md +1 -1
  84. package/skills/bugteam/CONSTRAINTS.md +3 -3
  85. package/skills/bugteam/PROMPTS.md +8 -19
  86. package/skills/bugteam/SKILL.md +13 -14
  87. package/skills/bugteam/reference/CLAUDE.md +1 -1
  88. package/skills/bugteam/reference/README.md +2 -3
  89. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  90. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  91. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  92. package/skills/bugteam/reference/team-setup.md +11 -11
  93. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  94. package/skills/bugteam/scripts/README.md +20 -8
  95. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  96. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  97. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  98. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  99. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  100. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  101. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  102. package/skills/closeout/SKILL.md +153 -0
  103. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  104. package/skills/closeout/reference/issue-body-templates.md +108 -0
  105. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  106. package/skills/copilot-finding-triage/SKILL.md +80 -28
  107. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  108. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  109. package/skills/everything-search/SKILL.md +1 -2
  110. package/skills/grokify/SKILL.md +58 -0
  111. package/skills/grokify/templates/handoff-template.md +92 -0
  112. package/skills/orchestrator/SKILL.md +35 -25
  113. package/skills/orchestrator-refresh/SKILL.md +15 -11
  114. package/skills/pr-converge/CLAUDE.md +1 -1
  115. package/skills/pr-converge/SKILL.md +132 -233
  116. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  117. package/skills/pr-converge/reference/examples.md +37 -25
  118. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  119. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  120. package/skills/pr-converge/reference/per-tick.md +64 -41
  121. package/skills/pr-converge/reference/state-schema.md +27 -2
  122. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  123. package/skills/pr-converge/scripts/README.md +4 -3
  124. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  125. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  126. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  127. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  128. package/skills/pr-converge/scripts/conftest.py +8 -0
  129. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  130. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  131. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  132. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  133. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  134. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  135. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  136. package/skills/pr-fix-protocol/SKILL.md +20 -20
  137. package/skills/privacy-hygiene/SKILL.md +4 -3
  138. package/skills/reviewer-gates/SKILL.md +26 -8
  139. package/skills/session-log/SKILL.md +11 -12
  140. package/skills/session-tidy/SKILL.md +40 -30
  141. package/skills/skill-builder/CLAUDE.md +13 -10
  142. package/skills/skill-builder/SKILL.md +31 -31
  143. package/skills/skill-builder/references/CLAUDE.md +10 -6
  144. package/skills/skill-builder/references/delegation-map.md +13 -4
  145. package/skills/skill-builder/references/description-field.md +113 -0
  146. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  147. package/skills/skill-builder/references/skill-modularity.md +113 -0
  148. package/skills/skill-builder/references/skill-types.md +4 -0
  149. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  150. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  151. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  152. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  153. package/skills/skill-builder/workflows/new-skill.md +34 -10
  154. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  155. package/skills/team-advisor/SKILL.md +15 -11
  156. package/skills/usage-pause/SKILL.md +10 -14
  157. package/system-prompts/software-engineer.xml +7 -6
  158. package/hooks/validators/test_verify_paths.py +0 -32
  159. package/hooks/validators/verify_paths.py +0 -57
  160. package/skills/bdd-protocol/CLAUDE.md +0 -26
  161. package/skills/bdd-protocol/SKILL.md +0 -30
  162. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  163. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  164. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  165. package/skills/bg-agent/CLAUDE.md +0 -17
  166. package/skills/bg-agent/SKILL.md +0 -69
  167. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  168. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  169. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  170. package/skills/caveman/CLAUDE.md +0 -15
  171. package/skills/caveman/SKILL.md +0 -38
  172. package/skills/code/CLAUDE.md +0 -17
  173. package/skills/code/SKILL.md +0 -178
  174. package/skills/deep-research/CLAUDE.md +0 -17
  175. package/skills/deep-research/SKILL.md +0 -80
  176. package/skills/findbugs/CLAUDE.md +0 -20
  177. package/skills/findbugs/SKILL.md +0 -241
  178. package/skills/fixbugs/CLAUDE.md +0 -19
  179. package/skills/fixbugs/SKILL.md +0 -142
  180. package/skills/gh-paginate/CLAUDE.md +0 -18
  181. package/skills/gh-paginate/SKILL.md +0 -84
  182. package/skills/gotcha/CLAUDE.md +0 -33
  183. package/skills/gotcha/SKILL.md +0 -73
  184. package/skills/implement/CLAUDE.md +0 -27
  185. package/skills/implement/SKILL.md +0 -66
  186. package/skills/implement/scripts/CLAUDE.md +0 -22
  187. package/skills/implement/scripts/append_note.py +0 -133
  188. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  189. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  190. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  191. package/skills/implement/scripts/test_append_note.py +0 -191
  192. package/skills/log-audit/CLAUDE.md +0 -20
  193. package/skills/log-audit/SKILL.md +0 -66
  194. package/skills/log-audit/reference/CLAUDE.md +0 -9
  195. package/skills/log-audit/reference/charter.md +0 -52
  196. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  197. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  198. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  199. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  200. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  201. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  202. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  203. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  204. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  205. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  206. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  207. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  208. package/skills/logifix/CLAUDE.md +0 -36
  209. package/skills/logifix/SKILL.md +0 -69
  210. package/skills/logifix/scripts/CLAUDE.md +0 -16
  211. package/skills/logifix/scripts/logifix.ps1 +0 -205
  212. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  213. package/skills/monitor-open-prs/SKILL.md +0 -88
  214. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  215. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  216. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  217. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  218. package/skills/post-audit-findings/SKILL.md +0 -80
  219. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  220. package/skills/pr-consistency-audit/SKILL.md +0 -116
  221. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  222. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  223. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  224. package/skills/pr-review-responder/CLAUDE.md +0 -35
  225. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  226. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  227. package/skills/pr-review-responder/README.md +0 -168
  228. package/skills/pr-review-responder/SKILL.md +0 -203
  229. package/skills/pr-review-responder/TESTING.md +0 -350
  230. package/skills/pr-scope-resolve/SKILL.md +0 -45
  231. package/skills/pre-compact/CLAUDE.md +0 -24
  232. package/skills/pre-compact/SKILL.md +0 -134
  233. package/skills/qbug/CLAUDE.md +0 -40
  234. package/skills/qbug/SKILL.md +0 -387
  235. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  236. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  237. package/skills/refine/CLAUDE.md +0 -44
  238. package/skills/refine/SKILL.md +0 -262
  239. package/skills/refine/templates/CLAUDE.md +0 -17
  240. package/skills/refine/templates/implementation-notes-template.html +0 -56
  241. package/skills/refine/templates/plan-template.md +0 -60
  242. package/skills/research-mode/CLAUDE.md +0 -35
  243. package/skills/research-mode/SKILL.md +0 -53
  244. package/skills/structure-prompt/CLAUDE.md +0 -42
  245. package/skills/structure-prompt/SKILL.md +0 -44
  246. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  247. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  248. package/skills/structure-prompt/reference/block-classification.md +0 -27
  249. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  250. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  251. package/skills/structure-prompt/reference/cleanup.md +0 -33
  252. package/skills/structure-prompt/reference/constraints.md +0 -33
  253. package/skills/structure-prompt/reference/directives.md +0 -37
  254. package/skills/structure-prompt/reference/examples.md +0 -72
  255. package/skills/structure-prompt/reference/instantiation.md +0 -51
  256. package/skills/structure-prompt/reference/output-contract.md +0 -72
  257. package/skills/structure-prompt/reference/per-category.md +0 -23
  258. package/skills/structure-prompt/reference/persona.md +0 -38
  259. package/skills/structure-prompt/reference/research.md +0 -33
  260. package/skills/structure-prompt/reference/structure.md +0 -28
  261. package/skills/verified-build/CLAUDE.md +0 -33
  262. 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")