claude-dev-env 1.38.1 → 1.40.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 (282) hide show
  1. package/CLAUDE.md +10 -36
  2. package/_shared/pr-loop/audit-reply-template.md +147 -0
  3. package/_shared/pr-loop/fix-protocol.md +25 -4
  4. package/_shared/pr-loop/gh-payloads.md +37 -50
  5. package/_shared/pr-loop/scripts/code_rules_gate.py +0 -60
  6. package/_shared/pr-loop/scripts/config/post_audit_thread_constants.py +199 -0
  7. package/_shared/pr-loop/scripts/config/reviews_disabled_constants.py +8 -0
  8. package/_shared/pr-loop/scripts/post_audit_thread.py +1242 -0
  9. package/_shared/pr-loop/scripts/preflight.py +129 -2
  10. package/_shared/pr-loop/scripts/reviews_disabled.py +59 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -19
  12. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +1116 -0
  13. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +127 -0
  14. package/_shared/pr-loop/scripts/tests/test_preflight.py +41 -0
  15. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +36 -0
  16. package/_shared/pr-loop/state-schema.md +1 -1
  17. package/agents/clean-coder.md +2 -2
  18. package/agents/pr-description-writer.md +150 -52
  19. package/bin/install.mjs +6 -7
  20. package/bin/install.test.mjs +8 -0
  21. package/commands/doc-gist.md +16 -0
  22. package/commands/plan.md +0 -2
  23. package/commands/review-plan.md +1 -1
  24. package/docs/CODE_RULES.md +122 -2
  25. package/docs/PR_DESCRIPTION_GUIDE.md +127 -64
  26. package/hooks/blocking/bot_mention_comment_blocker.py +75 -0
  27. package/hooks/blocking/code_rules_enforcer.py +1143 -129
  28. package/hooks/blocking/convergence_gate_blocker.py +130 -0
  29. package/hooks/blocking/destructive_command_blocker.py +74 -0
  30. package/hooks/blocking/gh_body_arg_blocker.py +30 -0
  31. package/hooks/blocking/md_to_html_blocker.py +119 -0
  32. package/hooks/blocking/pr_description_enforcer.py +57 -22
  33. package/hooks/blocking/test_bot_mention_comment_blocker.py +131 -0
  34. package/hooks/blocking/test_code_rules_enforcer.py +21 -0
  35. package/hooks/blocking/test_code_rules_enforcer_any_exempt_files.py +70 -0
  36. package/hooks/blocking/test_code_rules_enforcer_any_imports_and_cast.py +92 -0
  37. package/hooks/blocking/test_code_rules_enforcer_banned_import_alias.py +143 -0
  38. package/hooks/blocking/test_code_rules_enforcer_banned_prefixes.py +152 -0
  39. package/hooks/blocking/test_code_rules_enforcer_bare_except.py +120 -0
  40. package/hooks/blocking/test_code_rules_enforcer_boundary_types.py +175 -0
  41. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  42. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +50 -0
  43. package/hooks/blocking/test_code_rules_enforcer_docstring_format.py +255 -0
  44. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +130 -0
  45. package/hooks/blocking/test_code_rules_enforcer_stub_implementations.py +141 -0
  46. package/hooks/blocking/test_code_rules_enforcer_test_branching.py +143 -0
  47. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +169 -0
  48. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +99 -0
  49. package/hooks/blocking/test_code_rules_enforcer_typed_dict_pairs.py +141 -0
  50. package/hooks/blocking/test_convergence_gate_blocker.py +63 -0
  51. package/hooks/blocking/test_destructive_command_blocker.py +146 -0
  52. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +102 -0
  53. package/hooks/blocking/test_gh_body_arg_blocker.py +45 -0
  54. package/hooks/blocking/test_md_to_html_blocker.py +317 -0
  55. package/hooks/blocking/test_pr_description_enforcer.py +69 -8
  56. package/hooks/config/any_type_config.py +7 -0
  57. package/hooks/config/banned_identifiers_constants.py +11 -0
  58. package/hooks/config/blocking_check_limits.py +38 -0
  59. package/hooks/config/bot_mention_comment_blocker_constants.py +20 -0
  60. package/hooks/config/code_rules_enforcer_constants.py +53 -0
  61. package/hooks/config/convergence_branch_constants.py +9 -0
  62. package/hooks/config/doc_gist_auto_publish_constants.py +18 -0
  63. package/hooks/config/html_companion_constants.py +20 -0
  64. package/hooks/config/inline_tuple_string_magic_constants.py +22 -0
  65. package/hooks/config/pr_description_enforcer_constants.py +14 -0
  66. package/hooks/config/test_banned_identifiers_constants.py +17 -0
  67. package/hooks/hooks.json +28 -20
  68. package/hooks/pyproject.toml +69 -0
  69. package/hooks/validators/mypy_integration.py +47 -1
  70. package/hooks/validators/run_all_validators.py +3 -3
  71. package/hooks/validators/test_mypy_integration.py +50 -1
  72. package/hooks/workflow/doc_gist_auto_publish.py +144 -0
  73. package/hooks/workflow/md_to_html_companion.py +365 -0
  74. package/hooks/workflow/test_doc_gist_auto_publish.py +117 -0
  75. package/hooks/workflow/test_md_to_html_companion.py +452 -0
  76. package/package.json +1 -1
  77. package/rules/gh-body-file.md +2 -0
  78. package/scripts/Install-SweepEmptyDirs.ps1 +111 -0
  79. package/scripts/check.ps1 +106 -0
  80. package/scripts/config/timing.py +11 -0
  81. package/scripts/sweep_empty_dirs.py +138 -0
  82. package/scripts/sync_to_cursor/rules.py +1 -1
  83. package/scripts/test_sweep_empty_dirs.py +183 -0
  84. package/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +323 -0
  85. package/skills/_shared/pr-loop/scripts/_cli_utils.py +22 -0
  86. package/skills/_shared/pr-loop/scripts/_path_resolver.py +165 -0
  87. package/skills/_shared/pr-loop/scripts/_xml_utils.py +20 -0
  88. package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +182 -0
  89. package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +185 -0
  90. package/skills/_shared/pr-loop/scripts/config/__init__.py +0 -0
  91. package/skills/_shared/pr-loop/scripts/config/path_resolver_constants.py +78 -0
  92. package/skills/_shared/pr-loop/scripts/init_loop_state.py +135 -0
  93. package/skills/_shared/pr-loop/scripts/teardown_worktrees.py +175 -0
  94. package/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +182 -0
  95. package/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +206 -0
  96. package/skills/bugteam/CONSTRAINTS.md +21 -22
  97. package/skills/bugteam/EXAMPLES.md +3 -3
  98. package/skills/bugteam/PROMPTS.md +227 -67
  99. package/skills/bugteam/SKILL.md +132 -455
  100. package/skills/bugteam/reference/README.md +1 -1
  101. package/skills/bugteam/reference/audit-and-teammates.md +112 -39
  102. package/skills/bugteam/reference/audit-contract.md +4 -22
  103. package/skills/bugteam/reference/copilot-gap-analysis.md +8 -5
  104. package/skills/bugteam/reference/design-rationale.md +2 -2
  105. package/skills/bugteam/reference/github-pr-reviews.md +50 -57
  106. package/skills/bugteam/reference/obstacles/audit-assign-ids.md +13 -0
  107. package/skills/bugteam/reference/obstacles/audit-capture-excerpts.md +13 -0
  108. package/skills/bugteam/reference/obstacles/audit-walk-categories.md +13 -0
  109. package/skills/bugteam/reference/obstacles/audit-write-xml.md +13 -0
  110. package/skills/bugteam/reference/obstacles/fix-append-summary.md +13 -0
  111. package/skills/bugteam/reference/obstacles/fix-apply-fixes.md +13 -0
  112. package/skills/bugteam/reference/obstacles/fix-git-add-commit.md +13 -0
  113. package/skills/bugteam/reference/obstacles/fix-git-push.md +13 -0
  114. package/skills/bugteam/reference/obstacles/fix-post-reply.md +13 -0
  115. package/skills/bugteam/reference/obstacles/fix-publish-summary.md +13 -0
  116. package/skills/bugteam/reference/obstacles/fix-py-compile.md +13 -0
  117. package/skills/bugteam/reference/obstacles/fix-read-files.md +13 -0
  118. package/skills/bugteam/reference/obstacles/fix-resolve-thread.md +13 -0
  119. package/skills/bugteam/reference/obstacles/fix-test-suite.md +13 -0
  120. package/skills/bugteam/reference/obstacles/fix-violation-count.md +13 -0
  121. package/skills/bugteam/reference/obstacles/fix-write-xml.md +13 -0
  122. package/skills/bugteam/reference/team-setup.md +111 -9
  123. package/skills/bugteam/reference/teardown-publish-permissions.md +39 -8
  124. package/skills/bugteam/scripts/README.md +60 -0
  125. package/skills/bugteam/scripts/_claude_permissions_common.py +358 -0
  126. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +976 -0
  127. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +375 -0
  128. package/skills/bugteam/scripts/bugteam_preflight.py +328 -0
  129. package/skills/bugteam/scripts/config/bugteam_code_rules_gate_constants.py +25 -0
  130. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +26 -0
  131. package/skills/bugteam/scripts/config/bugteam_preflight_constants.py +35 -0
  132. package/skills/bugteam/scripts/config/claude_permissions_common_constants.py +20 -0
  133. package/skills/bugteam/scripts/config/probe_code_rules_enforcer_check_constants.py +12 -0
  134. package/skills/bugteam/scripts/config/windows_safe_rmtree_constants.py +7 -0
  135. package/skills/bugteam/scripts/grant_project_claude_permissions.py +175 -0
  136. package/skills/bugteam/scripts/probe_code_rules_enforcer_check.py +107 -0
  137. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +220 -0
  138. package/skills/bugteam/scripts/test__claude_permissions_common.py +112 -0
  139. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +400 -0
  140. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +384 -0
  141. package/skills/bugteam/scripts/test_bugteam_preflight.py +309 -0
  142. package/skills/bugteam/scripts/test_claude_permissions_common.py +195 -0
  143. package/skills/bugteam/scripts/test_grant_project_claude_permissions.py +55 -0
  144. package/skills/bugteam/scripts/test_probe_code_rules_enforcer_check.py +76 -0
  145. package/skills/bugteam/scripts/test_revoke_project_claude_permissions.py +55 -0
  146. package/skills/bugteam/scripts/test_windows_safe_rmtree.py +108 -0
  147. package/skills/bugteam/scripts/windows_safe_rmtree.py +100 -0
  148. package/skills/bugteam/test_skill_additions.py +1 -11
  149. package/skills/code/SKILL.md +176 -0
  150. package/skills/copilot-review/SKILL.md +16 -0
  151. package/skills/doc-gist/SKILL.md +99 -0
  152. package/skills/doc-gist/references/examples/01-exploration-code-approaches.html +453 -0
  153. package/skills/doc-gist/references/examples/02-exploration-visual-designs.html +515 -0
  154. package/skills/doc-gist/references/examples/03-code-review-pr.html +638 -0
  155. package/skills/doc-gist/references/examples/04-code-understanding.html +491 -0
  156. package/skills/doc-gist/references/examples/05-design-system.html +629 -0
  157. package/skills/doc-gist/references/examples/06-component-variants.html +605 -0
  158. package/skills/doc-gist/references/examples/07-prototype-animation.html +455 -0
  159. package/skills/doc-gist/references/examples/08-prototype-interaction.html +396 -0
  160. package/skills/doc-gist/references/examples/09-slide-deck.html +592 -0
  161. package/skills/doc-gist/references/examples/10-svg-illustrations.html +492 -0
  162. package/skills/doc-gist/references/examples/11-status-report.html +528 -0
  163. package/skills/doc-gist/references/examples/12-incident-report.html +596 -0
  164. package/skills/doc-gist/references/examples/13-flowchart-diagram.html +395 -0
  165. package/skills/doc-gist/references/examples/14-research-feature-explainer.html +381 -0
  166. package/skills/doc-gist/references/examples/15-research-concept-explainer.html +368 -0
  167. package/skills/doc-gist/references/examples/16-implementation-plan.html +702 -0
  168. package/skills/doc-gist/references/examples/17-pr-writeup.html +595 -0
  169. package/skills/doc-gist/references/examples/18-editor-triage-board.html +573 -0
  170. package/skills/doc-gist/references/examples/19-editor-feature-flags.html +663 -0
  171. package/skills/doc-gist/references/examples/20-editor-prompt-tuner.html +722 -0
  172. package/skills/doc-gist/references/examples/README.md +5 -0
  173. package/skills/doc-gist/scripts/config/__init__.py +0 -0
  174. package/skills/doc-gist/scripts/config/gist_upload_constants.py +16 -0
  175. package/skills/doc-gist/scripts/gist_upload.py +177 -0
  176. package/skills/doc-gist/scripts/test_gist_upload.py +51 -0
  177. package/skills/findbugs/SKILL.md +96 -2
  178. package/skills/monitor-open-prs/SKILL.md +14 -32
  179. package/skills/monitor-open-prs/test_skill_contract.py +0 -11
  180. package/skills/pr-consistency-audit/SKILL.md +112 -0
  181. package/skills/pr-consistency-audit/reference/detection-rules.md +96 -0
  182. package/skills/pr-consistency-audit/reference/illustrations.md +78 -0
  183. package/skills/pr-converge/SKILL.md +229 -23
  184. package/skills/pr-converge/config/__init__.py +0 -0
  185. package/skills/pr-converge/config/constants.py +63 -0
  186. package/skills/pr-converge/reference/convergence-gates.md +138 -44
  187. package/skills/pr-converge/reference/examples.md +43 -11
  188. package/skills/pr-converge/reference/fix-protocol.md +6 -5
  189. package/skills/pr-converge/reference/ground-rules.md +5 -3
  190. package/skills/pr-converge/reference/multi-pr-orchestration.md +44 -19
  191. package/skills/pr-converge/reference/obstacles/fix-post-replies.md +13 -0
  192. package/skills/pr-converge/reference/obstacles/fix-publish-summary.md +13 -0
  193. package/skills/pr-converge/reference/obstacles/fix-push.md +13 -0
  194. package/skills/pr-converge/reference/obstacles/fix-read-filelines.md +13 -0
  195. package/skills/pr-converge/reference/obstacles/fix-reset-state.md +13 -0
  196. package/skills/pr-converge/reference/obstacles/fix-resolve-threads.md +13 -0
  197. package/skills/pr-converge/reference/obstacles/fix-spawn-clean-coder.md +13 -0
  198. package/skills/pr-converge/reference/obstacles/fix-stage-commit.md +13 -0
  199. package/skills/pr-converge/reference/obstacles/fix-trigger-bugbot.md +13 -0
  200. package/skills/pr-converge/reference/obstacles/fix-write-test.md +13 -0
  201. package/skills/pr-converge/reference/per-tick.md +107 -31
  202. package/skills/pr-converge/reference/state-schema.md +22 -1
  203. package/skills/pr-converge/reference/stop-conditions.md +9 -7
  204. package/skills/pr-converge/scripts/README.md +34 -46
  205. package/skills/pr-converge/scripts/check_bugbot_ci.py +279 -0
  206. package/skills/pr-converge/scripts/check_convergence.py +497 -0
  207. package/skills/pr-converge/scripts/check_pending_reviews.py +154 -0
  208. package/skills/pr-converge/scripts/config/pr_converge_constants.py +118 -0
  209. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +134 -0
  210. package/skills/pr-converge/scripts/post_fix_reply.py +168 -0
  211. package/skills/pr-converge/scripts/test_check_bugbot_ci.py +312 -0
  212. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -12
  213. package/skills/qbug/SKILL.md +157 -27
  214. package/skills/session-log/SKILL.md +216 -114
  215. package/skills/session-tidy/SKILL.md +1 -1
  216. package/skills/skill-builder/SKILL.md +138 -56
  217. package/skills/skill-builder/references/delegation-map.md +72 -113
  218. package/skills/skill-builder/references/progressive-disclosure.md +122 -0
  219. package/skills/skill-builder/references/self-audit-checklist.md +92 -0
  220. package/skills/skill-builder/references/skill-types.md +228 -0
  221. package/skills/skill-builder/references/thariq-x-post-skills.json +33 -0
  222. package/skills/skill-builder/templates/gap-analysis.md +15 -8
  223. package/skills/skill-builder/workflows/improve-skill.md +86 -57
  224. package/skills/skill-builder/workflows/new-skill.md +80 -168
  225. package/skills/skill-builder/workflows/polish-skill.md +78 -54
  226. package/skills/structure-prompt/SKILL.md +50 -0
  227. package/skills/structure-prompt/reference/adversarial-tuning.md +62 -0
  228. package/skills/structure-prompt/reference/block-classification.md +27 -0
  229. package/skills/structure-prompt/reference/canonical-case.md +48 -0
  230. package/skills/structure-prompt/reference/citation-depth.md +70 -0
  231. package/skills/structure-prompt/reference/cleanup.md +33 -0
  232. package/skills/structure-prompt/reference/constraints.md +33 -0
  233. package/skills/structure-prompt/reference/directives.md +37 -0
  234. package/skills/structure-prompt/reference/examples.md +72 -0
  235. package/skills/structure-prompt/reference/instantiation.md +51 -0
  236. package/skills/structure-prompt/reference/output-contract.md +72 -0
  237. package/skills/structure-prompt/reference/per-category.md +23 -0
  238. package/skills/structure-prompt/reference/persona.md +38 -0
  239. package/skills/structure-prompt/reference/research.md +33 -0
  240. package/skills/structure-prompt/reference/structure.md +28 -0
  241. package/agents/code-standards-agent.md +0 -93
  242. package/agents/groq-coder.md +0 -113
  243. package/agents/plan-executor.md +0 -226
  244. package/agents/project-docs-analyzer.md +0 -53
  245. package/agents/project-structure-organizer-agent.md +0 -72
  246. package/agents/skill-to-agent-converter.md +0 -370
  247. package/agents/skill-writer-agent.md +0 -470
  248. package/agents/user-docs-writer.md +0 -67
  249. package/agents/workflow-visual-documenter.md +0 -82
  250. package/commands/readability-review.md +0 -20
  251. package/hooks/mypy.ini +0 -2
  252. package/hooks/notification/attention_needed_notify.py +0 -71
  253. package/hooks/notification/claude_notification_handler.py +0 -67
  254. package/hooks/notification/notification_utils.py +0 -267
  255. package/hooks/notification/subagent_complete_notify.py +0 -381
  256. package/hooks/notification/test_attention_needed_notify.py +0 -47
  257. package/hooks/notification/test_claude_notification_handler.py +0 -54
  258. package/hooks/notification/test_notification_utils.py +0 -91
  259. package/hooks/notification/test_subagent_complete_notify.py +0 -79
  260. package/scripts/config/groq_bugteam_config.py +0 -230
  261. package/scripts/config/test_groq_bugteam_config.py +0 -83
  262. package/scripts/config/test_spec_implementer_prompt.py +0 -32
  263. package/scripts/groq_bugteam.README.md +0 -131
  264. package/scripts/groq_bugteam.py +0 -647
  265. package/scripts/groq_bugteam_dotenv.py +0 -40
  266. package/scripts/groq_bugteam_spec.py +0 -226
  267. package/scripts/test_groq_bugteam.py +0 -529
  268. package/scripts/test_groq_bugteam_apply_fix_from_spec.py +0 -426
  269. package/scripts/test_groq_bugteam_dotenv.py +0 -66
  270. package/scripts/test_groq_bugteam_spec.py +0 -338
  271. package/skills/bugteam/SKILL_EVALS.md +0 -309
  272. package/skills/dream/SKILL.md +0 -118
  273. package/skills/ingest/SKILL.md +0 -40
  274. package/skills/npm-creator/SKILL.md +0 -187
  275. package/skills/readability-review/SKILL.md +0 -127
  276. package/skills/resume-review/SKILL.md +0 -261
  277. package/skills/rule-audit/SKILL.md +0 -307
  278. package/skills/rule-creator/SKILL.md +0 -150
  279. package/skills/searching-obsidian-vault/SKILL.md +0 -131
  280. package/skills/skill-writer/REFERENCE.md +0 -284
  281. package/skills/skill-writer/SKILL.md +0 -222
  282. package/skills/tdd-team/SKILL.md +0 -128
@@ -0,0 +1,375 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ for each_cached_module_name in [
9
+ each_module_key
10
+ for each_module_key in list(sys.modules)
11
+ if each_module_key == "config" or each_module_key.startswith("config.")
12
+ ]:
13
+ sys.modules.pop(each_cached_module_name, None)
14
+ if str(Path(__file__).resolve().parent) not in sys.path:
15
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
16
+
17
+ from config.bugteam_fix_hookspath_constants import (
18
+ ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS,
19
+ ALL_GLOBAL_HOOKS_PATH_ARGUMENTS,
20
+ ALL_HOME_ENV_VAR_NAMES,
21
+ GIT_DIRECTORY_NAME,
22
+ HOOKS_PATH_SUFFIX,
23
+ PREFLIGHT_NO_PYTEST_FLAG,
24
+ PREFLIGHT_REPO_ROOT_FLAG,
25
+ )
26
+
27
+
28
+ def _expected_hooks_path_suffix() -> str:
29
+ return HOOKS_PATH_SUFFIX
30
+
31
+
32
+ def _canonical_hooks_directory_components() -> tuple[str, str, str]:
33
+ return ALL_CANONICAL_HOOKS_DIRECTORY_COMPONENTS
34
+
35
+
36
+ def _home_env_var_names() -> tuple[str, str]:
37
+ return ALL_HOME_ENV_VAR_NAMES
38
+
39
+
40
+ def resolve_canonical_hooks_directory(
41
+ all_environment_overrides: dict[str, str] | None,
42
+ ) -> Path:
43
+ """Resolve the canonical hooks directory under the user's home directory.
44
+
45
+ When environment overrides are provided, checks HOME/USERPROFILE overrides
46
+ first before falling back to pathlib.Path.home().
47
+
48
+ Args:
49
+ all_environment_overrides: Optional dict of environment variable
50
+ overrides for resolving the home directory.
51
+
52
+ Returns:
53
+ The resolved Path to the canonical hooks directory.
54
+ """
55
+ components = _canonical_hooks_directory_components()
56
+ if all_environment_overrides is not None:
57
+ for each_env_var_name in _home_env_var_names():
58
+ home_value = all_environment_overrides.get(each_env_var_name)
59
+ if home_value:
60
+ return Path(home_value).joinpath(*components)
61
+ return Path.home().joinpath(*components)
62
+
63
+
64
+ def list_local_core_hooks_path_values(
65
+ repository_root: Path,
66
+ all_environment_overrides: dict[str, str] | None,
67
+ ) -> list[str]:
68
+ """Retrieve the local core.hooksPath values for a given repository.
69
+
70
+ Args:
71
+ repository_root: The repository root for running git config.
72
+ all_environment_overrides: Optional env overrides for git.
73
+
74
+ Returns:
75
+ List of local core.hooksPath strings, or empty list when unset.
76
+
77
+ Raises:
78
+ RuntimeError: When git emits a non-zero exit with non-empty stderr;
79
+ distinguishes a real git failure from the expected "key unset"
80
+ case (exit 1 with empty stderr).
81
+ """
82
+ git_command = [
83
+ "git",
84
+ "-C",
85
+ str(repository_root),
86
+ "config",
87
+ "--local",
88
+ "--get-all",
89
+ "core.hooksPath",
90
+ ]
91
+ completed_process = subprocess.run(
92
+ git_command,
93
+ capture_output=True,
94
+ text=True,
95
+ encoding="utf-8",
96
+ errors="replace",
97
+ check=False,
98
+ env=all_environment_overrides,
99
+ )
100
+ if completed_process.returncode != 0:
101
+ if completed_process.stderr.strip():
102
+ raise RuntimeError(
103
+ f"git config --local --get-all core.hooksPath failed on "
104
+ f"{repository_root} (exit {completed_process.returncode}): "
105
+ f"{completed_process.stderr.strip()}"
106
+ )
107
+ return []
108
+ return [
109
+ each_line.strip()
110
+ for each_line in completed_process.stdout.splitlines()
111
+ if each_line.strip()
112
+ ]
113
+
114
+
115
+ def read_global_core_hooks_path(
116
+ all_environment_overrides: dict[str, str] | None,
117
+ ) -> str:
118
+ """Read the global core.hooksPath git configuration value.
119
+
120
+ Args:
121
+ all_environment_overrides: Optional env overrides for git.
122
+
123
+ Returns:
124
+ The global core.hooksPath value, or empty string when unset.
125
+
126
+ Raises:
127
+ RuntimeError: When git emits a non-zero exit with non-empty stderr;
128
+ distinguishes a real git failure from the expected "key unset"
129
+ case (exit 1 with empty stderr).
130
+ """
131
+ completed_process = subprocess.run(
132
+ list(ALL_GLOBAL_HOOKS_PATH_ARGUMENTS),
133
+ capture_output=True,
134
+ text=True,
135
+ encoding="utf-8",
136
+ errors="replace",
137
+ check=False,
138
+ env=all_environment_overrides,
139
+ )
140
+ if completed_process.returncode != 0:
141
+ if completed_process.stderr.strip():
142
+ raise RuntimeError(
143
+ f"git config --global --get core.hooksPath failed "
144
+ f"(exit {completed_process.returncode}): "
145
+ f"{completed_process.stderr.strip()}"
146
+ )
147
+ return ""
148
+ return completed_process.stdout.strip()
149
+
150
+
151
+ def unset_local_core_hooks_path(
152
+ repository_root: Path,
153
+ all_environment_overrides: dict[str, str] | None,
154
+ ) -> int:
155
+ """Remove the local core.hooksPath configuration for a repository.
156
+
157
+ Args:
158
+ repository_root: The repository root for running git config.
159
+ all_environment_overrides: Optional env overrides for git.
160
+
161
+ Returns:
162
+ The git exit code (0 on success, non-zero on failure).
163
+ """
164
+ git_command = [
165
+ "git",
166
+ "-C",
167
+ str(repository_root),
168
+ "config",
169
+ "--local",
170
+ "--unset-all",
171
+ "core.hooksPath",
172
+ ]
173
+ completed_process = subprocess.run(
174
+ git_command,
175
+ capture_output=True,
176
+ text=True,
177
+ check=False,
178
+ env=all_environment_overrides,
179
+ )
180
+ return completed_process.returncode
181
+
182
+
183
+ def set_global_core_hooks_path(
184
+ target_value: str,
185
+ all_environment_overrides: dict[str, str] | None,
186
+ ) -> int:
187
+ """Set the global core.hooksPath git configuration value.
188
+
189
+ Args:
190
+ target_value: The hooks path value to set globally.
191
+ all_environment_overrides: Optional env overrides for git.
192
+
193
+ Returns:
194
+ The git exit code (0 on success, non-zero on failure).
195
+ """
196
+ git_command = ["git", "config", "--global", "core.hooksPath", target_value]
197
+ completed_process = subprocess.run(
198
+ git_command,
199
+ capture_output=True,
200
+ text=True,
201
+ check=False,
202
+ env=all_environment_overrides,
203
+ )
204
+ return completed_process.returncode
205
+
206
+
207
+ def normalize_hooks_path(raw_value: str) -> str:
208
+ return raw_value.replace("\\", "/").rstrip("/")
209
+
210
+
211
+ def is_canonical_hooks_path(raw_value: str) -> bool:
212
+ if not raw_value:
213
+ return False
214
+ return normalize_hooks_path(raw_value).endswith(_expected_hooks_path_suffix())
215
+
216
+
217
+ def find_repository_root(start: Path) -> Path:
218
+ """Find the repository root by walking up from the starting directory.
219
+
220
+ Searches for a ``.git`` directory or file in parent directories.
221
+
222
+ Args:
223
+ start: The directory to start searching from.
224
+
225
+ Returns:
226
+ The repository root path, or *start* when no repository is found.
227
+ """
228
+ resolved_start = start.resolve()
229
+ candidate_paths = [resolved_start, *resolved_start.parents]
230
+ for each_candidate in candidate_paths:
231
+ marker = each_candidate / GIT_DIRECTORY_NAME
232
+ if marker.is_dir() or marker.is_file():
233
+ return each_candidate
234
+ return resolved_start
235
+
236
+
237
+ def rerun_preflight(
238
+ repository_root: Path,
239
+ all_environment_overrides: dict[str, str] | None,
240
+ ) -> int:
241
+ """Re-run bugteam_preflight.py after fixing core.hooksPath.
242
+
243
+ Args:
244
+ repository_root: The repository root to pass to preflight.
245
+ all_environment_overrides: Optional env overrides for the subprocess.
246
+
247
+ Returns:
248
+ The preflight exit code (0 on success, non-zero on failure).
249
+ """
250
+ preflight_script_path = Path(__file__).resolve().parent / "bugteam_preflight.py"
251
+ rerun_command = [
252
+ sys.executable,
253
+ str(preflight_script_path),
254
+ PREFLIGHT_NO_PYTEST_FLAG,
255
+ PREFLIGHT_REPO_ROOT_FLAG,
256
+ str(repository_root),
257
+ ]
258
+ completed_process = subprocess.run(
259
+ rerun_command,
260
+ check=False,
261
+ env=all_environment_overrides,
262
+ )
263
+ return completed_process.returncode
264
+
265
+
266
+ def parse_arguments(all_argv: list[str] | None) -> argparse.Namespace:
267
+ """Parse command-line arguments for the hooks-path fix script.
268
+
269
+ Args:
270
+ all_argv: Command-line argument list (pass None for defaults).
271
+
272
+ Returns:
273
+ Parsed namespace with the repo_root attribute.
274
+ """
275
+ parser = argparse.ArgumentParser(
276
+ description=(
277
+ "Auto-fix core.hooksPath when bugteam preflight detects a stale override. "
278
+ "Removes a local-scope override and ensures global core.hooksPath points "
279
+ "at the canonical claude-dev-env git-hooks directory."
280
+ ),
281
+ )
282
+ parser.add_argument(
283
+ "--repo-root",
284
+ type=Path,
285
+ default=None,
286
+ help="Repository root (default: discover from cwd).",
287
+ )
288
+ return parser.parse_args(all_argv)
289
+
290
+
291
+ def main(
292
+ all_argv: list[str] | None,
293
+ *,
294
+ all_environment_overrides: dict[str, str] | None,
295
+ ) -> int:
296
+ """Fix core.hooksPath and rerun bugteam preflight.
297
+
298
+ Resolves the canonical hooks directory, checks for stale local overrides,
299
+ removes them if found, ensures global core.hooksPath is correct, then
300
+ reruns bugteam_preflight to verify.
301
+
302
+ Args:
303
+ all_argv: Command-line arguments to parse.
304
+ all_environment_overrides: Optional environment overrides for git.
305
+
306
+ Returns:
307
+ Zero on success, non-zero on failure.
308
+ """
309
+ arguments = parse_arguments(all_argv)
310
+ start_directory = Path.cwd()
311
+ repository_root = (
312
+ arguments.repo_root.resolve()
313
+ if arguments.repo_root is not None
314
+ else find_repository_root(start_directory)
315
+ )
316
+ canonical_hooks_directory = resolve_canonical_hooks_directory(all_environment_overrides)
317
+ expected_suffix = _expected_hooks_path_suffix()
318
+ if not canonical_hooks_directory.is_dir():
319
+ print(
320
+ "bugteam_fix_hookspath: canonical hooks directory does not exist: "
321
+ f"{canonical_hooks_directory}\n"
322
+ "Run: npx claude-dev-env .\n"
323
+ "Then re-run /bugteam. The directory must end in "
324
+ f"'{expected_suffix}' and contain the claude-dev-env git hook shims.",
325
+ file=sys.stderr,
326
+ )
327
+ return 1
328
+ local_hooks_path_values = list_local_core_hooks_path_values(
329
+ repository_root,
330
+ all_environment_overrides,
331
+ )
332
+ has_non_canonical_local_override = any(
333
+ not is_canonical_hooks_path(each_value)
334
+ for each_value in local_hooks_path_values
335
+ )
336
+ if has_non_canonical_local_override:
337
+ unset_local_returncode = unset_local_core_hooks_path(
338
+ repository_root, all_environment_overrides
339
+ )
340
+ if unset_local_returncode != 0:
341
+ print(
342
+ "bugteam_fix_hookspath: failed to unset local core.hooksPath on "
343
+ f"{repository_root} (git exit {unset_local_returncode}).",
344
+ file=sys.stderr,
345
+ )
346
+ return 1
347
+ print(
348
+ "bugteam_fix_hookspath: removed stale local core.hooksPath override on "
349
+ f"{repository_root}",
350
+ file=sys.stderr,
351
+ )
352
+ current_global_value = read_global_core_hooks_path(all_environment_overrides)
353
+ if not is_canonical_hooks_path(current_global_value):
354
+ canonical_target_value = str(canonical_hooks_directory).replace("\\", "/")
355
+ global_set_exit_code = set_global_core_hooks_path(
356
+ canonical_target_value,
357
+ all_environment_overrides,
358
+ )
359
+ if global_set_exit_code != 0:
360
+ print(
361
+ "bugteam_fix_hookspath: failed to set global core.hooksPath to "
362
+ f"{canonical_target_value} (git exit {global_set_exit_code}).",
363
+ file=sys.stderr,
364
+ )
365
+ return 1
366
+ print(
367
+ "bugteam_fix_hookspath: set global core.hooksPath to "
368
+ f"{canonical_target_value}",
369
+ file=sys.stderr,
370
+ )
371
+ return rerun_preflight(repository_root, all_environment_overrides)
372
+
373
+
374
+ if __name__ == "__main__":
375
+ raise SystemExit(main(None, all_environment_overrides=None))
@@ -0,0 +1,328 @@
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
+ for each_cached_module_name in [
10
+ each_module_key
11
+ for each_module_key in list(sys.modules)
12
+ if each_module_key == "config" or each_module_key.startswith("config.")
13
+ ]:
14
+ sys.modules.pop(each_cached_module_name, None)
15
+ _bugteam_scripts_directory = str(Path(__file__).absolute().parent)
16
+ while _bugteam_scripts_directory in sys.path:
17
+ sys.path.remove(_bugteam_scripts_directory)
18
+ if _bugteam_scripts_directory not in sys.path:
19
+ sys.path.insert(0, _bugteam_scripts_directory)
20
+
21
+ from config.bugteam_preflight_constants import (
22
+ ALL_DISCOVERY_IGNORE_DIRECTORIES,
23
+ ALL_GIT_CONFIG_HOOKS_PATH_ARGUMENTS,
24
+ ALL_PRE_COMMIT_ARGUMENTS,
25
+ BUGTEAM_PREFLIGHT_PREFIX,
26
+ BUGTEAM_PREFLIGHT_SKIP_ENV_VAR_NAME,
27
+ ENFORCEMENT_ABSENT_MESSAGE,
28
+ EXIT_CODE_HOOKS_PATH_CHECK_FAILED,
29
+ EXPECTED_HOOKS_PATH_SUFFIX,
30
+ GIT_DIRECTORY_NAME,
31
+ PRE_COMMIT_CONFIG_FILENAME,
32
+ PYPROJECT_FILENAME,
33
+ PYPROJECT_PYTEST_SECTION_PREFIX,
34
+ PYTEST_EXIT_CODE_NO_TESTS_COLLECTED,
35
+ PYTEST_INI_FILENAME,
36
+ )
37
+
38
+ for each_cached_module_name in [
39
+ each_module_key
40
+ for each_module_key in list(sys.modules)
41
+ if each_module_key == "config" or each_module_key.startswith("config.")
42
+ ]:
43
+ sys.modules.pop(each_cached_module_name, None)
44
+ _shared_pr_loop_scripts_directory = (
45
+ Path(__file__).absolute().parent
46
+ / ".." / ".." / ".." / "_shared" / "pr-loop" / "scripts"
47
+ ).absolute()
48
+ if str(_shared_pr_loop_scripts_directory) not in sys.path:
49
+ sys.path.insert(0, str(_shared_pr_loop_scripts_directory))
50
+
51
+ from reviews_disabled import (
52
+ CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN,
53
+ CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME,
54
+ EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV,
55
+ is_bugteam_disabled_via_env,
56
+ )
57
+
58
+
59
+ def verify_git_hooks_path(repository_root: Path | None) -> int:
60
+ """Check that core.hooksPath resolves to the claude-dev-env git-hooks directory.
61
+
62
+ When *repository_root* is provided, queries the effective config for that
63
+ repository (``git -C <root> config --get``), which detects repo-level
64
+ overrides such as Husky or lefthook. Falls back to the current working
65
+ directory's effective config when *repository_root* is None.
66
+
67
+ Args:
68
+ repository_root: Optional repository root to check. When None, uses
69
+ the current working directory's effective config.
70
+
71
+ Returns:
72
+ Zero when the configured path ends with the expected hooks suffix.
73
+ Non-zero and prints a correction message when unset or pointing elsewhere.
74
+ """
75
+ git_command: list[str] = ["git"]
76
+ if repository_root is not None:
77
+ git_command.extend(["-C", str(repository_root)])
78
+ git_command.extend(list(ALL_GIT_CONFIG_HOOKS_PATH_ARGUMENTS))
79
+ try:
80
+ query_result = subprocess.run(
81
+ git_command,
82
+ capture_output=True,
83
+ text=True,
84
+ encoding="utf-8",
85
+ errors="replace",
86
+ check=False,
87
+ )
88
+ except FileNotFoundError:
89
+ print(
90
+ f"{BUGTEAM_PREFLIGHT_PREFIX}git is not installed or not available on PATH.\n"
91
+ f"{ENFORCEMENT_ABSENT_MESSAGE}",
92
+ file=sys.stderr,
93
+ )
94
+ return EXIT_CODE_HOOKS_PATH_CHECK_FAILED
95
+ except OSError as os_error:
96
+ print(
97
+ f"{BUGTEAM_PREFLIGHT_PREFIX}failed to run git: {os_error}\n"
98
+ f"{ENFORCEMENT_ABSENT_MESSAGE}",
99
+ file=sys.stderr,
100
+ )
101
+ return EXIT_CODE_HOOKS_PATH_CHECK_FAILED
102
+ if query_result.returncode != 0:
103
+ print(
104
+ f"{BUGTEAM_PREFLIGHT_PREFIX}{ENFORCEMENT_ABSENT_MESSAGE}",
105
+ file=sys.stderr,
106
+ )
107
+ return EXIT_CODE_HOOKS_PATH_CHECK_FAILED
108
+ configured_path = query_result.stdout.strip().replace("\\", "/").rstrip("/")
109
+ if not configured_path.endswith(EXPECTED_HOOKS_PATH_SUFFIX):
110
+ print(
111
+ f"{BUGTEAM_PREFLIGHT_PREFIX}core.hooksPath is '{configured_path}' — "
112
+ f"expected path ending in '{EXPECTED_HOOKS_PATH_SUFFIX}'.\n"
113
+ f"{ENFORCEMENT_ABSENT_MESSAGE}",
114
+ file=sys.stderr,
115
+ )
116
+ return EXIT_CODE_HOOKS_PATH_CHECK_FAILED
117
+ return 0
118
+
119
+
120
+ def find_repository_root(start: Path) -> Path:
121
+ """Find the repository root by walking up from the starting directory.
122
+
123
+ Searches for a ``.git`` directory or file in parent directories. Falls
124
+ back to the nearest ancestor containing ``pytest.ini`` when no git
125
+ repository is found.
126
+
127
+ Args:
128
+ start: The directory to start searching from.
129
+
130
+ Returns:
131
+ The repository root path, or *start* when no repository is found.
132
+ """
133
+ resolved = start.resolve()
134
+ candidates = [resolved, *resolved.parents]
135
+ for each_candidate in candidates:
136
+ if (each_candidate / GIT_DIRECTORY_NAME).is_dir() or (each_candidate / GIT_DIRECTORY_NAME).is_file():
137
+ return each_candidate
138
+ for each_candidate in candidates:
139
+ if (each_candidate / PYTEST_INI_FILENAME).is_file():
140
+ return each_candidate
141
+ return resolved
142
+
143
+
144
+ def has_pytest_configuration(root: Path) -> bool:
145
+ """Check whether a directory has pytest configuration available.
146
+
147
+ Checks for ``pytest.ini`` directly, then falls back to searching for
148
+ ``[tool.pytest]`` in ``pyproject.toml``.
149
+
150
+ Args:
151
+ root: The directory to check for pytest configuration.
152
+
153
+ Returns:
154
+ True when pytest configuration is found in either location.
155
+ """
156
+ if (root / PYTEST_INI_FILENAME).is_file():
157
+ return True
158
+ pyproject = root / PYPROJECT_FILENAME
159
+ if not pyproject.is_file():
160
+ return False
161
+ text = pyproject.read_text(encoding="utf-8", errors="replace")
162
+ return PYPROJECT_PYTEST_SECTION_PREFIX in text
163
+
164
+
165
+ def has_discoverable_tests(root: Path) -> bool:
166
+ """Check whether the directory tree contains discoverable test files.
167
+
168
+ Searches for files matching ``test_*.py`` and ``*_test.py`` patterns,
169
+ skipping directories in the configured ignore list (virtual environments,
170
+ node_modules).
171
+
172
+ Args:
173
+ root: The directory tree root to search.
174
+
175
+ Returns:
176
+ True when at least one test file is found outside ignored directories.
177
+ """
178
+ for each_path in root.rglob("test_*.py"):
179
+ if any(part_dir in ALL_DISCOVERY_IGNORE_DIRECTORIES for part_dir in each_path.parts):
180
+ continue
181
+ return True
182
+ for each_path in root.rglob("*_test.py"):
183
+ if any(part_dir in ALL_DISCOVERY_IGNORE_DIRECTORIES for part_dir in each_path.parts):
184
+ continue
185
+ return True
186
+ return False
187
+
188
+
189
+ def _pytest_exit_code_no_tests_collected() -> int:
190
+ return PYTEST_EXIT_CODE_NO_TESTS_COLLECTED
191
+
192
+
193
+ def run_pytest(repository_root: Path, verbose: bool) -> int:
194
+ """Run pytest in the repository root and return the exit code.
195
+
196
+ Treats the "no tests collected" exit code as a pass (exit 0).
197
+
198
+ Args:
199
+ repository_root: The repository root for running pytest.
200
+ verbose: When True, pass no -q flag (shows individual test names).
201
+
202
+ Returns:
203
+ The pytest exit code, or 0 when no tests were collected.
204
+ """
205
+ command = [sys.executable, "-m", "pytest"]
206
+ if not verbose:
207
+ command.append("-q")
208
+ completed = subprocess.run(
209
+ command,
210
+ cwd=str(repository_root),
211
+ check=False,
212
+ )
213
+ if completed.returncode == _pytest_exit_code_no_tests_collected():
214
+ return 0
215
+ return completed.returncode
216
+
217
+
218
+ def run_pre_commit(repository_root: Path) -> int:
219
+ """Run pre-commit on all files and return the exit code.
220
+
221
+ Args:
222
+ repository_root: The repository root for running pre-commit.
223
+
224
+ Returns:
225
+ The pre-commit exit code (0 on success, non-zero on failure).
226
+ """
227
+ completed = subprocess.run(
228
+ ALL_PRE_COMMIT_ARGUMENTS,
229
+ cwd=str(repository_root),
230
+ check=False,
231
+ )
232
+ return completed.returncode
233
+
234
+
235
+ def parse_arguments(all_argv: list[str]) -> argparse.Namespace:
236
+ """Parse command-line arguments for the preflight script.
237
+
238
+ Args:
239
+ all_argv: Command-line argument list.
240
+
241
+ Returns:
242
+ Parsed namespace with repo_root, no_pytest, pre_commit, and verbose.
243
+ """
244
+ parser = argparse.ArgumentParser(
245
+ description="Run local checks before /bugteam (pytest, optional pre-commit).",
246
+ )
247
+ parser.add_argument(
248
+ "--repo-root",
249
+ type=Path,
250
+ default=None,
251
+ help="Repository root (default: discover from cwd).",
252
+ )
253
+ parser.add_argument(
254
+ "--no-pytest",
255
+ action="store_true",
256
+ help="Skip pytest.",
257
+ )
258
+ parser.add_argument(
259
+ "--pre-commit",
260
+ action="store_true",
261
+ help="Run pre-commit when .pre-commit-config.yaml exists.",
262
+ )
263
+ parser.add_argument(
264
+ "-v",
265
+ "--verbose",
266
+ action="store_true",
267
+ help="Verbose pytest output.",
268
+ )
269
+ return parser.parse_args(all_argv)
270
+
271
+
272
+ def main(all_argv: list[str] | None = None) -> int:
273
+ """Run the bugteam preflight checks (pytest, optional pre-commit).
274
+
275
+ Args:
276
+ all_argv: Command-line arguments to parse. Pass None to use sys.argv.
277
+
278
+ Returns:
279
+ Zero on success, non-zero exit code on failure.
280
+ """
281
+ arguments = parse_arguments(sys.argv[1:] if all_argv is None else all_argv)
282
+ if os.environ.get(BUGTEAM_PREFLIGHT_SKIP_ENV_VAR_NAME, "").strip() == "1":
283
+ print(f"{BUGTEAM_PREFLIGHT_PREFIX}skipped (BUGTEAM_PREFLIGHT_SKIP=1).", file=sys.stderr)
284
+ return 0
285
+ reviews_disabled_env_var_name = CLAUDE_REVIEWS_DISABLED_ENV_VAR_NAME
286
+ reviews_disabled_bugteam_token = CLAUDE_REVIEWS_DISABLED_BUGTEAM_TOKEN
287
+ disabled_via_env_exit_code = EXIT_CODE_BUGTEAM_DISABLED_VIA_ENV
288
+ if is_bugteam_disabled_via_env():
289
+ print(
290
+ f"{BUGTEAM_PREFLIGHT_PREFIX}halted "
291
+ f"({reviews_disabled_env_var_name} contains "
292
+ f"'{reviews_disabled_bugteam_token}').",
293
+ file=sys.stderr,
294
+ )
295
+ return disabled_via_env_exit_code
296
+ start = Path.cwd()
297
+ resolved_repository_root: Path = (
298
+ arguments.repo_root.resolve()
299
+ if arguments.repo_root is not None
300
+ else find_repository_root(start)
301
+ )
302
+ hooks_path_exit_code = verify_git_hooks_path(resolved_repository_root)
303
+ if hooks_path_exit_code != 0:
304
+ return hooks_path_exit_code
305
+ if not arguments.no_pytest and has_pytest_configuration(resolved_repository_root):
306
+ if not has_discoverable_tests(resolved_repository_root):
307
+ print(
308
+ f"{BUGTEAM_PREFLIGHT_PREFIX}pytest configured but no tests found; skipping pytest.",
309
+ file=sys.stderr,
310
+ )
311
+ else:
312
+ exit_code = run_pytest(resolved_repository_root, arguments.verbose)
313
+ if exit_code != 0:
314
+ return exit_code
315
+ elif not arguments.no_pytest:
316
+ print(
317
+ f"{BUGTEAM_PREFLIGHT_PREFIX}no pytest configuration found; skipping pytest.",
318
+ file=sys.stderr,
319
+ )
320
+ if arguments.pre_commit and (resolved_repository_root / PRE_COMMIT_CONFIG_FILENAME).is_file():
321
+ exit_code = run_pre_commit(resolved_repository_root)
322
+ if exit_code != 0:
323
+ return exit_code
324
+ return 0
325
+
326
+
327
+ if __name__ == "__main__":
328
+ raise SystemExit(main(sys.argv[1:]))