claude-dev-env 1.35.0 → 1.36.1

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 (115) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/bin/install.mjs +28 -8
  3. package/bin/install.test.mjs +9 -1
  4. package/docs/CODE_RULES.md +3 -0
  5. package/docs/agents-md-alignment-plan.md +123 -0
  6. package/hooks/blocking/code_rules_enforcer.py +451 -39
  7. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  8. package/hooks/blocking/test_code_rules_enforcer.py +182 -0
  9. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  10. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
  12. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
  13. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  14. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
  15. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
  16. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  17. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  18. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  19. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  20. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  21. package/hooks/config/banned_identifiers_constants.py +24 -0
  22. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  23. package/hooks/config/hook_log_extractor_constants.py +1 -1
  24. package/hooks/config/pre_tool_use_stdin.py +48 -0
  25. package/hooks/config/setup_project_paths_constants.py +4 -0
  26. package/hooks/config/stuttering_check_config.py +14 -0
  27. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  28. package/hooks/config/sys_path_insert_constants.py +4 -0
  29. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  30. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  31. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  32. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  33. package/hooks/config/unused_module_import_constants.py +7 -0
  34. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  35. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  36. package/hooks/git-hooks/config.py +3 -3
  37. package/hooks/git-hooks/test_gate_utils.py +10 -10
  38. package/hooks/mypy.ini +2 -0
  39. package/package.json +1 -1
  40. package/rules/gh-paginate.md +125 -0
  41. package/skills/bugteam/CONSTRAINTS.md +12 -6
  42. package/skills/bugteam/SKILL.md +364 -154
  43. package/skills/bugteam/SKILL_EVALS.md +25 -23
  44. package/skills/bugteam/reference/README.md +2 -0
  45. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  46. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  47. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  48. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  49. package/skills/bugteam/scripts/reflow_skill_md.py +298 -0
  50. package/skills/bugteam/test_skill_additions.py +13 -4
  51. package/skills/bugteam/test_team_lifecycle.py +103 -0
  52. package/skills/findbugs/SKILL.md +3 -3
  53. package/skills/fixbugs/SKILL.md +4 -4
  54. package/skills/monitor-open-prs/SKILL.md +32 -2
  55. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  56. package/skills/pr-converge/SKILL.md +1206 -131
  57. package/skills/pr-converge/scripts/README.md +145 -0
  58. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  59. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  60. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  61. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  62. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  63. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  64. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  65. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  66. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  67. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  68. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  69. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  70. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  71. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  72. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  73. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  74. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  75. package/skills/pr-converge/scripts/reflow_skill_md.py +288 -0
  76. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  77. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  78. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  79. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  80. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  81. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  82. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  83. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  84. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  85. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  86. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  87. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  88. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  89. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  90. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  91. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  92. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  93. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  94. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  95. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  96. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  97. package/skills/pr-converge/test_team_lifecycle.py +56 -0
  98. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  99. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  100. package/skills/qbug/SKILL.md +4 -4
  101. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  102. package/skills/resume-review/SKILL.md +261 -0
  103. package/skills/bugteam/scripts/README.md +0 -58
  104. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  105. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  106. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  107. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  108. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  109. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  110. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  111. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  112. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  113. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  114. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  115. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -1,260 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import argparse
4
- import subprocess
5
- import sys
6
- from pathlib import Path
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
-
20
-
21
- def _expected_hooks_path_suffix() -> str:
22
- return HOOKS_PATH_SUFFIX
23
-
24
-
25
- def _canonical_hooks_directory_components() -> tuple[str, str, str]:
26
- return ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS
27
-
28
-
29
- def _home_env_var_names() -> tuple[str, str]:
30
- return ALL_HOME_ENV_VAR_NAMES
31
-
32
-
33
- def resolve_canonical_hooks_directory(
34
- environment_overrides: dict[str, str] | None,
35
- ) -> Path:
36
- components = _canonical_hooks_directory_components()
37
- if environment_overrides is not None:
38
- for each_env_var_name in _home_env_var_names():
39
- home_value = environment_overrides.get(each_env_var_name)
40
- if home_value:
41
- return Path(home_value).joinpath(*components)
42
- return Path.home().joinpath(*components)
43
-
44
-
45
- def list_local_core_hooks_path_values(
46
- repository_root: Path,
47
- environment_overrides: dict[str, str] | None,
48
- ) -> list[str]:
49
- git_command = [
50
- "git",
51
- "-C",
52
- str(repository_root),
53
- "config",
54
- "--local",
55
- "--get-all",
56
- "core.hooksPath",
57
- ]
58
- completed_process = subprocess.run(
59
- git_command,
60
- capture_output=True,
61
- text=True,
62
- encoding="utf-8",
63
- errors="replace",
64
- check=False,
65
- env=environment_overrides,
66
- )
67
- if completed_process.returncode != 0:
68
- return []
69
- return [
70
- each_line.strip()
71
- for each_line in completed_process.stdout.splitlines()
72
- if each_line.strip()
73
- ]
74
-
75
-
76
- def read_global_core_hooks_path(
77
- environment_overrides: dict[str, str] | None,
78
- ) -> str:
79
- git_command = ["git", "config", "--global", "--get", "core.hooksPath"]
80
- completed_process = subprocess.run(
81
- git_command,
82
- capture_output=True,
83
- text=True,
84
- encoding="utf-8",
85
- errors="replace",
86
- check=False,
87
- env=environment_overrides,
88
- )
89
- if completed_process.returncode != 0:
90
- return ""
91
- return completed_process.stdout.strip()
92
-
93
-
94
- def unset_local_core_hooks_path(
95
- repository_root: Path,
96
- environment_overrides: dict[str, str] | None,
97
- ) -> int:
98
- git_command = [
99
- "git",
100
- "-C",
101
- str(repository_root),
102
- "config",
103
- "--local",
104
- "--unset-all",
105
- "core.hooksPath",
106
- ]
107
- completed_process = subprocess.run(
108
- git_command,
109
- capture_output=True,
110
- text=True,
111
- check=False,
112
- env=environment_overrides,
113
- )
114
- return completed_process.returncode
115
-
116
-
117
- def set_global_core_hooks_path(
118
- target_value: str,
119
- environment_overrides: dict[str, str] | None,
120
- ) -> int:
121
- git_command = ["git", "config", "--global", "core.hooksPath", target_value]
122
- completed_process = subprocess.run(
123
- git_command,
124
- capture_output=True,
125
- text=True,
126
- check=False,
127
- env=environment_overrides,
128
- )
129
- return completed_process.returncode
130
-
131
-
132
- def normalize_hooks_path(raw_value: str) -> str:
133
- return raw_value.replace("\\", "/").rstrip("/")
134
-
135
-
136
- def is_canonical_hooks_path(raw_value: str) -> bool:
137
- if not raw_value:
138
- return False
139
- return normalize_hooks_path(raw_value).endswith(_expected_hooks_path_suffix())
140
-
141
-
142
- def find_repository_root(start: Path) -> Path:
143
- resolved_start = start.resolve()
144
- candidate_paths = [resolved_start, *resolved_start.parents]
145
- for each_candidate in candidate_paths:
146
- marker = each_candidate / ".git"
147
- if marker.is_dir() or marker.is_file():
148
- return each_candidate
149
- return resolved_start
150
-
151
-
152
- def rerun_preflight(
153
- repository_root: Path,
154
- environment_overrides: dict[str, str] | None,
155
- ) -> int:
156
- preflight_script_path = Path(__file__).resolve().parent / "bugteam_preflight.py"
157
- rerun_command = [
158
- sys.executable,
159
- str(preflight_script_path),
160
- PREFLIGHT_NO_PYTEST_FLAG,
161
- PREFLIGHT_REPO_ROOT_FLAG,
162
- str(repository_root),
163
- ]
164
- completed_process = subprocess.run(
165
- rerun_command,
166
- check=False,
167
- env=environment_overrides,
168
- )
169
- return completed_process.returncode
170
-
171
-
172
- def parse_arguments(argv: list[str] | None) -> argparse.Namespace:
173
- parser = argparse.ArgumentParser(
174
- description=(
175
- "Auto-fix core.hooksPath when bugteam preflight detects a stale override. "
176
- "Removes a local-scope override and ensures global core.hooksPath points "
177
- "at the canonical claude-dev-env git-hooks directory."
178
- ),
179
- )
180
- parser.add_argument(
181
- "--repo-root",
182
- type=Path,
183
- default=None,
184
- help="Repository root (default: discover from cwd).",
185
- )
186
- return parser.parse_args(argv)
187
-
188
-
189
- def main(
190
- argv: list[str] | None = None,
191
- *,
192
- environment_overrides: dict[str, str] | None = None,
193
- ) -> int:
194
- arguments = parse_arguments(argv)
195
- start_directory = Path.cwd()
196
- repository_root = (
197
- arguments.repo_root.resolve()
198
- if arguments.repo_root is not None
199
- else find_repository_root(start_directory)
200
- )
201
- canonical_hooks_directory = resolve_canonical_hooks_directory(environment_overrides)
202
- expected_suffix = _expected_hooks_path_suffix()
203
- if not canonical_hooks_directory.is_dir():
204
- print(
205
- "bugteam_fix_hookspath: canonical hooks directory does not exist: "
206
- f"{canonical_hooks_directory}\n"
207
- "Run: npx claude-dev-env .\n"
208
- "Then re-run /bugteam. The directory must end in "
209
- f"'{expected_suffix}' and contain the claude-dev-env git hook shims.",
210
- file=sys.stderr,
211
- )
212
- return 1
213
- local_hooks_path_values = list_local_core_hooks_path_values(
214
- repository_root,
215
- environment_overrides,
216
- )
217
- has_non_canonical_local_override = any(
218
- not is_canonical_hooks_path(each_value)
219
- for each_value in local_hooks_path_values
220
- )
221
- if has_non_canonical_local_override:
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
232
- print(
233
- "bugteam_fix_hookspath: removed stale local core.hooksPath override on "
234
- f"{repository_root}",
235
- file=sys.stderr,
236
- )
237
- current_global_value = read_global_core_hooks_path(environment_overrides)
238
- if not is_canonical_hooks_path(current_global_value):
239
- canonical_target_value = str(canonical_hooks_directory).replace("\\", "/")
240
- global_set_exit_code = set_global_core_hooks_path(
241
- canonical_target_value,
242
- environment_overrides,
243
- )
244
- if global_set_exit_code != 0:
245
- print(
246
- "bugteam_fix_hookspath: failed to set global core.hooksPath to "
247
- f"{canonical_target_value} (git exit {global_set_exit_code}).",
248
- file=sys.stderr,
249
- )
250
- return 1
251
- print(
252
- "bugteam_fix_hookspath: set global core.hooksPath to "
253
- f"{canonical_target_value}",
254
- file=sys.stderr,
255
- )
256
- return rerun_preflight(repository_root, environment_overrides)
257
-
258
-
259
- if __name__ == "__main__":
260
- raise SystemExit(main())
@@ -1,201 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import argparse
4
- import os
5
- import subprocess
6
- import sys
7
- from pathlib import Path
8
-
9
-
10
- def verify_git_hooks_path(repository_root: Path | None = None) -> int:
11
- """Check that core.hooksPath resolves to the claude-dev-env git-hooks directory.
12
-
13
- When *repository_root* is provided, queries the effective config for that
14
- repository (``git -C <root> config --get``), which detects repo-level
15
- overrides such as Husky or lefthook. Falls back to the current working
16
- directory's effective config when *repository_root* is None.
17
-
18
- Returns zero when the configured path ends with the expected hooks suffix.
19
- Returns non-zero and prints a correction message when unset or pointing elsewhere.
20
- """
21
- expected_hooks_path_suffix = "hooks/git-hooks"
22
- enforcement_absent_message = (
23
- "Git-side CODE_RULES enforcement is not active on this host.\n"
24
- "Run: npx claude-dev-env .\n"
25
- "Or set core.hooksPath at any scope, e.g.:\n"
26
- " git config --global core.hooksPath ~/.claude/hooks/git-hooks"
27
- )
28
- git_command: list[str] = ["git"]
29
- if repository_root is not None:
30
- git_command.extend(["-C", str(repository_root)])
31
- git_command.extend(["config", "--get", "core.hooksPath"])
32
- try:
33
- query_result = subprocess.run(
34
- git_command,
35
- capture_output=True,
36
- text=True,
37
- encoding="utf-8",
38
- errors="replace",
39
- check=False,
40
- )
41
- except FileNotFoundError:
42
- print(
43
- "bugteam_preflight: git is not installed or not available on PATH.\n"
44
- f"{enforcement_absent_message}",
45
- file=sys.stderr,
46
- )
47
- return 1
48
- except OSError as os_error:
49
- print(
50
- f"bugteam_preflight: failed to run git: {os_error}\n"
51
- f"{enforcement_absent_message}",
52
- file=sys.stderr,
53
- )
54
- return 1
55
- if query_result.returncode != 0:
56
- print(
57
- f"bugteam_preflight: {enforcement_absent_message}",
58
- file=sys.stderr,
59
- )
60
- return 1
61
- configured_path = query_result.stdout.strip().replace("\\", "/").rstrip("/")
62
- if not configured_path.endswith(expected_hooks_path_suffix):
63
- print(
64
- f"bugteam_preflight: core.hooksPath is '{configured_path}' — "
65
- f"expected path ending in '{expected_hooks_path_suffix}'.\n"
66
- f"{enforcement_absent_message}",
67
- file=sys.stderr,
68
- )
69
- return 1
70
- return 0
71
-
72
-
73
- def find_repository_root(start: Path) -> Path:
74
- resolved = start.resolve()
75
- candidates = [resolved, *resolved.parents]
76
- for candidate in candidates:
77
- if (candidate / ".git").is_dir() or (candidate / ".git").is_file():
78
- return candidate
79
- for candidate in candidates:
80
- if (candidate / "pytest.ini").is_file():
81
- return candidate
82
- return resolved
83
-
84
-
85
- def has_pytest_configuration(root: Path) -> bool:
86
- if (root / "pytest.ini").is_file():
87
- return True
88
- pyproject = root / "pyproject.toml"
89
- if not pyproject.is_file():
90
- return False
91
- text = pyproject.read_text(encoding="utf-8", errors="replace")
92
- return "[tool.pytest" in text
93
-
94
-
95
- def has_discoverable_tests(root: Path) -> bool:
96
- ignore = {"site-packages", ".venv", "venv", "node_modules"}
97
- for path in root.rglob("test_*.py"):
98
- if any(part in ignore for part in path.parts):
99
- continue
100
- return True
101
- for path in root.rglob("*_test.py"):
102
- if any(part in ignore for part in path.parts):
103
- continue
104
- return True
105
- return False
106
-
107
-
108
- def _pytest_exit_code_no_tests_collected() -> int:
109
- return 5
110
-
111
-
112
- def run_pytest(repository_root: Path, verbose: bool) -> int:
113
- command = [sys.executable, "-m", "pytest"]
114
- if not verbose:
115
- command.append("-q")
116
- completed = subprocess.run(
117
- command,
118
- cwd=str(repository_root),
119
- check=False,
120
- )
121
- if completed.returncode == _pytest_exit_code_no_tests_collected():
122
- return 0
123
- return completed.returncode
124
-
125
-
126
- def run_pre_commit(repository_root: Path) -> int:
127
- completed = subprocess.run(
128
- ["pre-commit", "run", "--all-files"],
129
- cwd=str(repository_root),
130
- check=False,
131
- )
132
- return completed.returncode
133
-
134
-
135
- def parse_arguments(argv: list[str]) -> argparse.Namespace:
136
- parser = argparse.ArgumentParser(
137
- description="Run local checks before /bugteam (pytest, optional pre-commit).",
138
- )
139
- parser.add_argument(
140
- "--repo-root",
141
- type=Path,
142
- default=None,
143
- help="Repository root (default: discover from cwd).",
144
- )
145
- parser.add_argument(
146
- "--no-pytest",
147
- action="store_true",
148
- help="Skip pytest.",
149
- )
150
- parser.add_argument(
151
- "--pre-commit",
152
- action="store_true",
153
- help="Run pre-commit when .pre-commit-config.yaml exists.",
154
- )
155
- parser.add_argument(
156
- "-v",
157
- "--verbose",
158
- action="store_true",
159
- help="Verbose pytest output.",
160
- )
161
- return parser.parse_args(argv)
162
-
163
-
164
- def main(argv: list[str] | None = None) -> int:
165
- arguments = parse_arguments(sys.argv[1:] if argv is None else argv)
166
- if os.environ.get("BUGTEAM_PREFLIGHT_SKIP", "").strip() == "1":
167
- print("bugteam_preflight: skipped (BUGTEAM_PREFLIGHT_SKIP=1).", file=sys.stderr)
168
- return 0
169
- start = Path.cwd()
170
- repository_root = (
171
- arguments.repo_root.resolve()
172
- if arguments.repo_root is not None
173
- else find_repository_root(start)
174
- )
175
- hooks_path_exit_code = verify_git_hooks_path(repository_root)
176
- if hooks_path_exit_code != 0:
177
- return hooks_path_exit_code
178
- if not arguments.no_pytest and has_pytest_configuration(repository_root):
179
- if not has_discoverable_tests(repository_root):
180
- print(
181
- "bugteam_preflight: pytest configured but no tests found; skipping pytest.",
182
- file=sys.stderr,
183
- )
184
- else:
185
- exit_code = run_pytest(repository_root, arguments.verbose)
186
- if exit_code != 0:
187
- return exit_code
188
- elif not arguments.no_pytest:
189
- print(
190
- "bugteam_preflight: no pytest configuration found; skipping pytest.",
191
- file=sys.stderr,
192
- )
193
- if arguments.pre_commit and (repository_root / ".pre-commit-config.yaml").is_file():
194
- exit_code = run_pre_commit(repository_root)
195
- if exit_code != 0:
196
- return exit_code
197
- return 0
198
-
199
-
200
- if __name__ == "__main__":
201
- raise SystemExit(main())
@@ -1,17 +0,0 @@
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"
@@ -1,109 +0,0 @@
1
- """Grant Edit/Write/Read permissions on the current directory's .claude tree.
2
-
3
- Run from the project root whose .claude/** you want a Claude Code session
4
- (including spawned subagents) to edit without prompting. Writes idempotent
5
- entries into the user-scope settings at ~/.claude/settings.json and prints
6
- the changes applied. No-op when the entries already exist.
7
- """
8
-
9
- import sys
10
- from pathlib import Path
11
-
12
- sys.path.insert(0, str(Path(__file__).resolve().parent))
13
-
14
- from _claude_permissions_common import ( # noqa: E402
15
- append_if_missing,
16
- build_permission_rules,
17
- ensure_dict_section,
18
- ensure_list_entry,
19
- exit_with_error,
20
- get_current_project_path,
21
- load_settings,
22
- save_settings,
23
- AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE,
24
- PERMISSION_ALLOW_TOOLS,
25
- )
26
-
27
-
28
- def is_valid_project_root(candidate_path: Path) -> bool:
29
- git_marker_path = candidate_path / ".git"
30
- claude_marker_path = candidate_path / ".claude"
31
- return git_marker_path.exists() or claude_marker_path.exists()
32
-
33
-
34
- def add_rules_to_allow_list(settings: dict[str, object], rules_to_add: list[str]) -> int:
35
- permissions_section = ensure_dict_section(settings, "permissions")
36
- existing_allow_list = ensure_list_entry(permissions_section, "allow")
37
- return sum(
38
- 1
39
- for each_rule in rules_to_add
40
- if append_if_missing(existing_allow_list, each_rule)
41
- )
42
-
43
-
44
- def add_directory_to_additional_directories(
45
- settings: dict[str, object], directory_path: str
46
- ) -> int:
47
- permissions_section = ensure_dict_section(settings, "permissions")
48
- existing_directories = ensure_list_entry(
49
- permissions_section, "additionalDirectories"
50
- )
51
- if append_if_missing(existing_directories, directory_path):
52
- return 1
53
- return 0
54
-
55
-
56
- def add_auto_mode_environment_entry(
57
- settings: dict[str, object], entry_text: str
58
- ) -> int:
59
- auto_mode_section = ensure_dict_section(settings, "autoMode")
60
- existing_environment = ensure_list_entry(auto_mode_section, "environment")
61
- if append_if_missing(existing_environment, entry_text):
62
- return 1
63
- return 0
64
-
65
-
66
- def grant_permissions_for_current_directory() -> None:
67
- claude_user_settings_path: Path = Path.home() / ".claude" / "settings.json"
68
- project_root_path = Path.cwd()
69
- if not is_valid_project_root(project_root_path):
70
- print(
71
- f"ERROR: cwd {project_root_path} is not a project root "
72
- f"(no .git or .claude). Run from a project root.",
73
- file=sys.stderr,
74
- )
75
- raise SystemExit(1)
76
- project_path = get_current_project_path()
77
- permission_rules = build_permission_rules(project_path, PERMISSION_ALLOW_TOOLS)
78
- environment_entry = AUTO_MODE_ENVIRONMENT_ENTRY_TEMPLATE.format(
79
- project_path=project_path
80
- )
81
- settings = load_settings(claude_user_settings_path)
82
- rules_added_count = add_rules_to_allow_list(settings, permission_rules)
83
- directories_added_count = add_directory_to_additional_directories(
84
- settings, project_path
85
- )
86
- environment_entries_added_count = add_auto_mode_environment_entry(
87
- settings, environment_entry
88
- )
89
- total_changes_count = (
90
- rules_added_count + directories_added_count + environment_entries_added_count
91
- )
92
- if total_changes_count == 0:
93
- print(f"Project path: {project_path}")
94
- print(f"Settings file: {claude_user_settings_path}")
95
- print("No changes needed; settings file left untouched.")
96
- return
97
- save_settings(claude_user_settings_path, settings)
98
- print(f"Project path: {project_path}")
99
- print(f"Settings file: {claude_user_settings_path}")
100
- print(f"Allow rules added: {rules_added_count} of {len(permission_rules)}")
101
- print(f"Additional directories added: {directories_added_count}")
102
- print(f"Auto-mode environment entries added: {environment_entries_added_count}")
103
-
104
-
105
- if __name__ == "__main__":
106
- try:
107
- grant_permissions_for_current_directory()
108
- except ValueError as path_error:
109
- exit_with_error(str(path_error))