claude-dev-env 1.0.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 (215) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/agents/agent-writer.md +157 -0
  4. package/agents/clasp-deployment-orchestrator.md +609 -0
  5. package/agents/clean-coder.md +295 -0
  6. package/agents/code-quality-agent.md +40 -0
  7. package/agents/code-standards-agent.md +93 -0
  8. package/agents/config-centralizer.md +686 -0
  9. package/agents/config-extraction-agent.md +225 -0
  10. package/agents/doc-orchestrator.md +47 -0
  11. package/agents/docs-agent.md +112 -0
  12. package/agents/docx-agent.md +211 -0
  13. package/agents/git-commit-crafter.md +100 -0
  14. package/agents/magic-value-eliminator-agent.md +72 -0
  15. package/agents/mandatory-agent-workflow-agent.md +88 -0
  16. package/agents/parallel-workflow-coordinator.md +779 -0
  17. package/agents/pdf-agent.md +302 -0
  18. package/agents/plan-executor.md +226 -0
  19. package/agents/pr-description-writer.md +87 -0
  20. package/agents/project-context-loader.md +238 -0
  21. package/agents/project-docs-analyzer.md +54 -0
  22. package/agents/project-structure-organizer-agent.md +72 -0
  23. package/agents/readability-review-agent.md +76 -0
  24. package/agents/refactoring-specialist.md +69 -0
  25. package/agents/right-sized-engineer.md +129 -0
  26. package/agents/session-continuity-manager.md +53 -0
  27. package/agents/skill-to-agent-converter.md +371 -0
  28. package/agents/skill-writer-agent.md +470 -0
  29. package/agents/stub-detector-agent.md +140 -0
  30. package/agents/tdd-test-writer.md +62 -0
  31. package/agents/test-data-builder.md +68 -0
  32. package/agents/tooling-builder.md +78 -0
  33. package/agents/user-docs-writer.md +67 -0
  34. package/agents/validation-expert.md +71 -0
  35. package/agents/workflow-visual-documenter.md +82 -0
  36. package/agents/xlsx-agent.md +169 -0
  37. package/bin/install.mjs +256 -0
  38. package/commands/commit.md +28 -0
  39. package/commands/docupdate.md +322 -0
  40. package/commands/implement.md +102 -0
  41. package/commands/initialize.md +91 -0
  42. package/commands/plan.md +63 -0
  43. package/commands/pr-comments.md +47 -0
  44. package/commands/readability-review.md +20 -0
  45. package/commands/review-plan.md +7 -0
  46. package/commands/right-size.md +15 -0
  47. package/commands/stubcheck.md +89 -0
  48. package/commands/sum.md +30 -0
  49. package/docs/CODE_RULES.md +186 -0
  50. package/docs/DJANGO_PATTERNS.md +80 -0
  51. package/docs/REACT_PATTERNS.md +185 -0
  52. package/docs/TEST_QUALITY.md +104 -0
  53. package/hooks/advisory/migration-safety-advisor.py +49 -0
  54. package/hooks/advisory/refactor-guard.py +205 -0
  55. package/hooks/blocking/block-main-commit.py +168 -0
  56. package/hooks/blocking/code-rules-enforcer.py +549 -0
  57. package/hooks/blocking/destructive-command-blocker.py +107 -0
  58. package/hooks/blocking/docker-settings-guard.py +44 -0
  59. package/hooks/blocking/hedging-language-blocker.py +130 -0
  60. package/hooks/blocking/parallel-task-blocker.py +69 -0
  61. package/hooks/blocking/pr-description-enforcer.py +87 -0
  62. package/hooks/blocking/pyautogui-scroll-blocker.py +74 -0
  63. package/hooks/blocking/sensitive-file-protector.py +70 -0
  64. package/hooks/blocking/tdd-enforcer.py +62 -0
  65. package/hooks/blocking/test-preflight-check.py +343 -0
  66. package/hooks/blocking/write-existing-file-blocker.py +63 -0
  67. package/hooks/git-hooks/post-commit.py +103 -0
  68. package/hooks/github-action/test_workflow.py +33 -0
  69. package/hooks/hooks.json +246 -0
  70. package/hooks/lifecycle/config-change-guard.py +84 -0
  71. package/hooks/lifecycle/session-end-cleanup.py +59 -0
  72. package/hooks/notification/attention-needed-notify.py +63 -0
  73. package/hooks/notification/claude-notification-handler.py +59 -0
  74. package/hooks/notification/notification_utils.py +206 -0
  75. package/hooks/rewrite-plugin-paths.py +116 -0
  76. package/hooks/session/bulk-edit-reminder.py +30 -0
  77. package/hooks/session/code-rules-reminder.py +97 -0
  78. package/hooks/session/compact-context-reinject.py +39 -0
  79. package/hooks/session/hook-structure-context.py +140 -0
  80. package/hooks/session/plugin-data-dir-cleanup.py +39 -0
  81. package/hooks/validation/code-style-validator.py +145 -0
  82. package/hooks/validation/e2e-test-validator.py +142 -0
  83. package/hooks/validation/hook-format-validator.py +66 -0
  84. package/hooks/validation/mypy_validator.py +180 -0
  85. package/hooks/validators/README.md +125 -0
  86. package/hooks/validators/VALIDATION_REPORT.md +287 -0
  87. package/hooks/validators/__init__.py +19 -0
  88. package/hooks/validators/abbreviation_checks.py +82 -0
  89. package/hooks/validators/code_quality_checks.py +133 -0
  90. package/hooks/validators/comment_checks.py +188 -0
  91. package/hooks/validators/file_structure_checks.py +182 -0
  92. package/hooks/validators/git_checks.py +107 -0
  93. package/hooks/validators/health_check.py +214 -0
  94. package/hooks/validators/magic_value_checks.py +81 -0
  95. package/hooks/validators/mypy_integration.py +52 -0
  96. package/hooks/validators/output_formatter.py +266 -0
  97. package/hooks/validators/pr_reference_checks.py +72 -0
  98. package/hooks/validators/python_antipattern_checks.py +110 -0
  99. package/hooks/validators/python_style_checks.py +364 -0
  100. package/hooks/validators/react_checks.py +90 -0
  101. package/hooks/validators/ruff_integration.py +80 -0
  102. package/hooks/validators/run_all_validators.py +772 -0
  103. package/hooks/validators/security_checks.py +135 -0
  104. package/hooks/validators/test_abbreviation_checks.py +76 -0
  105. package/hooks/validators/test_bad.tsx +7 -0
  106. package/hooks/validators/test_code_quality_checks.py +129 -0
  107. package/hooks/validators/test_file_structure_checks.py +307 -0
  108. package/hooks/validators/test_files/01_basic_component.tsx +10 -0
  109. package/hooks/validators/test_files/02_component_without_react.tsx +10 -0
  110. package/hooks/validators/test_files/03_pure_component.tsx +10 -0
  111. package/hooks/validators/test_files/04_pure_component_import.tsx +10 -0
  112. package/hooks/validators/test_files/05_typescript_generics.tsx +14 -0
  113. package/hooks/validators/test_files/06_typescript_two_generics.tsx +18 -0
  114. package/hooks/validators/test_files/07_multiline_declaration.tsx +11 -0
  115. package/hooks/validators/test_files/08_error_boundary_valid.tsx +14 -0
  116. package/hooks/validators/test_files/09_error_boundary_with_other_class.tsx +20 -0
  117. package/hooks/validators/test_files/10_inheritance_chain.tsx +16 -0
  118. package/hooks/validators/test_files/11_ts_file.ts +10 -0
  119. package/hooks/validators/test_files/12_non_react_class.tsx +14 -0
  120. package/hooks/validators/test_files/13_functional_component.tsx +8 -0
  121. package/hooks/validators/test_files/14_indented_class.tsx +13 -0
  122. package/hooks/validators/test_files/15_getDerivedStateFromError.tsx +14 -0
  123. package/hooks/validators/test_files/16_mixed_components.tsx +20 -0
  124. package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +175 -0
  125. package/hooks/validators/test_files/TEST_RESULTS_TABLE.txt +60 -0
  126. package/hooks/validators/test_files/VALIDATION_REPORT.md +201 -0
  127. package/hooks/validators/test_files/async_views.py +23 -0
  128. package/hooks/validators/test_files/async_with_imports.py +14 -0
  129. package/hooks/validators/test_files/bad_inline_imports.py +37 -0
  130. package/hooks/validators/test_files/management/commands/cmd_01_no_debug_check.py +10 -0
  131. package/hooks/validators/test_files/management/commands/cmd_02_proper_debug_check.py +14 -0
  132. package/hooks/validators/test_files/management/commands/cmd_03_debug_check_with_return.py +14 -0
  133. package/hooks/validators/test_files/management/commands/cmd_04_imported_DEBUG.py +14 -0
  134. package/hooks/validators/test_files/management/commands/cmd_05_debug_check_in_helper.py +16 -0
  135. package/hooks/validators/test_files/management/commands/cmd_06_debug_check_late.py +22 -0
  136. package/hooks/validators/test_files/management/commands/cmd_07_positive_debug_check.py +15 -0
  137. package/hooks/validators/test_files/management/commands/cmd_08_debug_with_and.py +14 -0
  138. package/hooks/validators/test_files/not_management_command.py +10 -0
  139. package/hooks/validators/test_files/skip_decorators/test_01_simple_skip.py +8 -0
  140. package/hooks/validators/test_files/skip_decorators/test_02_pytest_skipif.py +8 -0
  141. package/hooks/validators/test_files/skip_decorators/test_03_unittest_skipIf.py +8 -0
  142. package/hooks/validators/test_files/skip_decorators/test_04_skip_with_parens.py +8 -0
  143. package/hooks/validators/test_files/skip_decorators/test_05_xfail.py +7 -0
  144. package/hooks/validators/test_files/skip_decorators/test_06_custom_skip.py +11 -0
  145. package/hooks/validators/test_files/skip_decorators/test_07_capital_Skip.py +8 -0
  146. package/hooks/validators/test_files/skip_decorators/test_08_skipUnless.py +7 -0
  147. package/hooks/validators/test_files/skip_decorators/test_09_pytest_mark_skip_simple.py +7 -0
  148. package/hooks/validators/test_files/test_async_functions.py +45 -0
  149. package/hooks/validators/test_files/test_purecomponent/PureComponentExample.tsx +7 -0
  150. package/hooks/validators/test_files/test_purecomponent/ReactPureComponentExample.tsx +7 -0
  151. package/hooks/validators/test_git_checks.py +295 -0
  152. package/hooks/validators/test_good.tsx +5 -0
  153. package/hooks/validators/test_health_check.py +57 -0
  154. package/hooks/validators/test_magic_value_checks.py +63 -0
  155. package/hooks/validators/test_mypy_integration.py +27 -0
  156. package/hooks/validators/test_output_formatter.py +150 -0
  157. package/hooks/validators/test_pr_reference_checks.py +41 -0
  158. package/hooks/validators/test_python_antipattern_checks.py +113 -0
  159. package/hooks/validators/test_python_style_checks.py +439 -0
  160. package/hooks/validators/test_react_checks.py +213 -0
  161. package/hooks/validators/test_results.txt +25 -0
  162. package/hooks/validators/test_ruff_integration.py +27 -0
  163. package/hooks/validators/test_run_all_validators.py +228 -0
  164. package/hooks/validators/test_run_all_validators_integration.py +48 -0
  165. package/hooks/validators/test_safety_checks.py +243 -0
  166. package/hooks/validators/test_security_checks.py +105 -0
  167. package/hooks/validators/test_test_safety_checks.py +321 -0
  168. package/hooks/validators/test_todo_checks.py +39 -0
  169. package/hooks/validators/test_type_safety_checks.py +85 -0
  170. package/hooks/validators/test_useless_test_checks.py +55 -0
  171. package/hooks/validators/test_validator_base.py +26 -0
  172. package/hooks/validators/test_verify_paths.py +34 -0
  173. package/hooks/validators/todo_checks.py +59 -0
  174. package/hooks/validators/type_safety_checks.py +101 -0
  175. package/hooks/validators/useless_test_checks.py +92 -0
  176. package/hooks/validators/validator_base.py +19 -0
  177. package/hooks/validators/verify_paths.py +57 -0
  178. package/hooks/workflow/auto-formatter.py +114 -0
  179. package/hooks/workflow/investigation-tracker-reset.py +46 -0
  180. package/package.json +30 -0
  181. package/rules/agent-spawn-protocol.md +47 -0
  182. package/rules/cleanup-temp-files.md +27 -0
  183. package/rules/code-reviews.md +11 -0
  184. package/rules/code-standards.md +43 -0
  185. package/rules/conservative-action.md +20 -0
  186. package/rules/context7.md +12 -0
  187. package/rules/explore-thoroughly.md +27 -0
  188. package/rules/git-workflow.md +42 -0
  189. package/rules/parallel-tools.md +23 -0
  190. package/rules/research-mode.md +23 -0
  191. package/rules/right-sized-engineering.md +28 -0
  192. package/rules/tdd.md +7 -0
  193. package/rules/testing.md +12 -0
  194. package/skills/agent-prompt/SKILL.md +102 -0
  195. package/skills/anthropic-plan/SKILL.md +107 -0
  196. package/skills/everything-search/SKILL.md +144 -0
  197. package/skills/ingest/SKILL.md +40 -0
  198. package/skills/npm-creator/SKILL.md +183 -0
  199. package/skills/pr-review-responder/EXAMPLES.md +590 -0
  200. package/skills/pr-review-responder/PRINCIPLES.md +539 -0
  201. package/skills/pr-review-responder/README.md +209 -0
  202. package/skills/pr-review-responder/SKILL.md +202 -0
  203. package/skills/pr-review-responder/TESTING.md +407 -0
  204. package/skills/pr-review-responder/scripts/respond_to_reviews.py +376 -0
  205. package/skills/pr-review-responder/update_skill.py +297 -0
  206. package/skills/prompt-generator/REFERENCE.md +150 -0
  207. package/skills/prompt-generator/SKILL.md +154 -0
  208. package/skills/readability-review/SKILL.md +127 -0
  209. package/skills/recall/SKILL.md +27 -0
  210. package/skills/remember/SKILL.md +63 -0
  211. package/skills/rule-audit/SKILL.md +307 -0
  212. package/skills/rule-creator/SKILL.md +150 -0
  213. package/skills/skill-writer/REFERENCE.md +246 -0
  214. package/skills/skill-writer/SKILL.md +270 -0
  215. package/skills/tdd-team/SKILL.md +128 -0
@@ -0,0 +1,772 @@
1
+ """Run all pre-push validators and report results.
2
+
3
+ This script orchestrates all automated validators and produces a unified report.
4
+ Exit code 0 = all checks pass, 1 = violations found.
5
+ """
6
+
7
+ import argparse
8
+ import subprocess
9
+ import sys
10
+ import time
11
+ from dataclasses import dataclass
12
+ from datetime import datetime
13
+ from pathlib import Path
14
+ from typing import Any, Callable, Dict, List, Optional, Tuple
15
+
16
+ from health_check import get_validator_version
17
+ from mypy_integration import check_mypy_available, run_mypy_check
18
+ from output_formatter import OutputFormatter, OutputMode, ValidatorResultDict
19
+ from ruff_integration import check_ruff_available, run_ruff_check
20
+
21
+
22
+ VALIDATORS_DIR = Path(__file__).parent
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class TimingMetrics:
27
+ """Timing information for validator runs (immutable)."""
28
+
29
+ total_seconds: float
30
+ validator_times: Dict[str, float]
31
+
32
+
33
+ def create_timing_metrics(validator_times: Dict[str, float]) -> TimingMetrics:
34
+ """Create a TimingMetrics instance from validator times.
35
+
36
+ Args:
37
+ validator_times: Dict mapping validator names to elapsed seconds
38
+
39
+ Returns:
40
+ TimingMetrics with calculated total
41
+ """
42
+ total = sum(validator_times.values())
43
+ return TimingMetrics(
44
+ total_seconds=total,
45
+ validator_times=dict(validator_times),
46
+ )
47
+
48
+
49
+ def add_timing(metrics: TimingMetrics, name: str, seconds: float) -> TimingMetrics:
50
+ """Add a timing entry, returning a new TimingMetrics instance.
51
+
52
+ Args:
53
+ metrics: Existing TimingMetrics
54
+ name: Validator name
55
+ seconds: Elapsed time in seconds
56
+
57
+ Returns:
58
+ New TimingMetrics with added entry
59
+ """
60
+ new_times = dict(metrics.validator_times)
61
+ new_times[name] = seconds
62
+ return create_timing_metrics(new_times)
63
+
64
+
65
+ def format_timing_report(metrics: TimingMetrics) -> str:
66
+ """Format timing metrics as a report string.
67
+
68
+ Args:
69
+ metrics: TimingMetrics to format
70
+
71
+ Returns:
72
+ Formatted report string
73
+ """
74
+ lines = ["", "Timing:"]
75
+ for name, seconds in sorted(metrics.validator_times.items(), key=lambda x: -x[1]):
76
+ lines.append(f" {name}: {seconds:.3f}s")
77
+ lines.append(f" Total: {metrics.total_seconds:.3f}s")
78
+ return "\n".join(lines)
79
+
80
+
81
+ def print_header() -> None:
82
+ """Print the header with version information."""
83
+ version = get_validator_version()
84
+ print("=" * 60)
85
+ print(f"PRE-PUSH VALIDATOR RESULTS (v{version})")
86
+ print("=" * 60)
87
+
88
+
89
+ def build_json_output(
90
+ results: List[Any],
91
+ metrics: TimingMetrics,
92
+ include_timing: bool,
93
+ ) -> Dict[str, Any]:
94
+ """Build JSON output dictionary.
95
+
96
+ Args:
97
+ results: List of validator results
98
+ metrics: TimingMetrics instance
99
+ include_timing: Whether to include timing data
100
+
101
+ Returns:
102
+ Dict suitable for JSON serialization
103
+ """
104
+ return {
105
+ "version": get_validator_version(),
106
+ "timestamp": datetime.now().isoformat(),
107
+ "results": [
108
+ {"name": r.name, "checks": r.checks, "passed": r.passed, "output": r.output}
109
+ for r in results
110
+ ],
111
+ "timing": metrics.validator_times if include_timing else None,
112
+ }
113
+
114
+
115
+ @dataclass(frozen=True)
116
+ class ValidatorResult:
117
+ """Result from running a validator."""
118
+
119
+ name: str
120
+ checks: str
121
+ passed: bool
122
+ output: str
123
+ skipped: bool = False
124
+
125
+
126
+ def run_with_fallback(
127
+ validator_func: Callable[[], ValidatorResult],
128
+ fallback_name: str,
129
+ fallback_checks: str,
130
+ ) -> ValidatorResult:
131
+ """Run a validator with graceful error handling.
132
+
133
+ Args:
134
+ validator_func: Validator function to run
135
+ fallback_name: Name to use in error result
136
+ fallback_checks: Check numbers for error result
137
+
138
+ Returns:
139
+ ValidatorResult, either from validator or skipped fallback
140
+ """
141
+ try:
142
+ return validator_func()
143
+ except FileNotFoundError as error:
144
+ return ValidatorResult(
145
+ name=fallback_name,
146
+ checks=fallback_checks,
147
+ passed=False,
148
+ output=f"Validator not found: {error} (skipped)",
149
+ skipped=True,
150
+ )
151
+ except Exception as error:
152
+ return ValidatorResult(
153
+ name=fallback_name,
154
+ checks=fallback_checks,
155
+ passed=False,
156
+ output=f"Validator error: {error} (skipped)",
157
+ skipped=True,
158
+ )
159
+
160
+
161
+ def run_python_style_checks(files: List[Path]) -> ValidatorResult:
162
+ """Run Python style checks on files."""
163
+ py_files = [f for f in files if f.suffix == ".py"]
164
+ if not py_files:
165
+ return ValidatorResult(
166
+ name="Python Style",
167
+ checks="1,2,3,4",
168
+ passed=True,
169
+ output="No Python files to check",
170
+ )
171
+
172
+ result = subprocess.run(
173
+ [sys.executable, str(VALIDATORS_DIR / "python_style_checks.py")]
174
+ + [str(f) for f in py_files],
175
+ capture_output=True,
176
+ text=True,
177
+ )
178
+
179
+ return ValidatorResult(
180
+ name="Python Style",
181
+ checks="1,2,3,4",
182
+ passed=result.returncode == 0,
183
+ output=result.stdout or "All checks passed",
184
+ )
185
+
186
+
187
+ def run_test_safety_checks(files: List[Path]) -> ValidatorResult:
188
+ """Run test safety checks on test files."""
189
+ test_files = [f for f in files if "test" in f.name.lower() and f.suffix == ".py"]
190
+ if not test_files:
191
+ return ValidatorResult(
192
+ name="Test Safety",
193
+ checks="11,21",
194
+ passed=True,
195
+ output="No test files to check",
196
+ )
197
+
198
+ result = subprocess.run(
199
+ [sys.executable, str(VALIDATORS_DIR / "test_safety_checks.py")]
200
+ + [str(f) for f in test_files],
201
+ capture_output=True,
202
+ text=True,
203
+ )
204
+
205
+ return ValidatorResult(
206
+ name="Test Safety",
207
+ checks="11,21",
208
+ passed=result.returncode == 0,
209
+ output=result.stdout or "All checks passed",
210
+ )
211
+
212
+
213
+ def get_project_root() -> Optional[Path]:
214
+ """Get project root by finding git root."""
215
+ result = subprocess.run(
216
+ ["git", "rev-parse", "--show-toplevel"],
217
+ capture_output=True,
218
+ text=True,
219
+ )
220
+ if result.returncode == 0:
221
+ return Path(result.stdout.strip())
222
+ return None
223
+
224
+
225
+ def run_file_structure_checks(project_root: Optional[Path] = None) -> ValidatorResult:
226
+ """Run file structure checks on project."""
227
+ if project_root is None:
228
+ project_root = get_project_root()
229
+
230
+ if project_root is None:
231
+ return ValidatorResult(
232
+ name="File Structure",
233
+ checks="14,15",
234
+ passed=True,
235
+ output="Not in a git repository - skipping",
236
+ )
237
+
238
+ result = subprocess.run(
239
+ [sys.executable, str(VALIDATORS_DIR / "file_structure_checks.py"), str(project_root)],
240
+ capture_output=True,
241
+ text=True,
242
+ )
243
+
244
+ return ValidatorResult(
245
+ name="File Structure",
246
+ checks="14,15",
247
+ passed=result.returncode == 0,
248
+ output=result.stdout or "All checks passed",
249
+ )
250
+
251
+
252
+ def run_react_checks(files: List[Path]) -> ValidatorResult:
253
+ """Run React checks on TSX/JSX files."""
254
+ react_files = [f for f in files if f.suffix in (".tsx", ".jsx")]
255
+ if not react_files:
256
+ return ValidatorResult(
257
+ name="React",
258
+ checks="17",
259
+ passed=True,
260
+ output="No React files to check",
261
+ )
262
+
263
+ result = subprocess.run(
264
+ [sys.executable, str(VALIDATORS_DIR / "react_checks.py")]
265
+ + [str(f) for f in react_files],
266
+ capture_output=True,
267
+ text=True,
268
+ )
269
+
270
+ return ValidatorResult(
271
+ name="React",
272
+ checks="17",
273
+ passed=result.returncode == 0,
274
+ output=result.stdout or "All checks passed",
275
+ )
276
+
277
+
278
+ def run_git_checks() -> ValidatorResult:
279
+ """Run git/GitHub checks."""
280
+ result = subprocess.run(
281
+ [sys.executable, str(VALIDATORS_DIR / "git_checks.py")],
282
+ capture_output=True,
283
+ text=True,
284
+ )
285
+
286
+ return ValidatorResult(
287
+ name="Git/PR Workflow",
288
+ checks="23,24",
289
+ passed=result.returncode == 0,
290
+ output=result.stdout or "All checks passed",
291
+ )
292
+
293
+
294
+ def run_comment_checks(files: List[Path]) -> ValidatorResult:
295
+ """Comment preservation is enforced by code-rules-enforcer hook.
296
+
297
+ The hook compares old vs new content to block NEW comments and
298
+ block DELETION of existing comments. This standalone validator
299
+ is disabled because it flags ALL comments in existing files,
300
+ which forces agents to remove them to pass validation.
301
+ """
302
+ return ValidatorResult(
303
+ name="No Comments",
304
+ checks="26",
305
+ passed=True,
306
+ output="Handled by code-rules-enforcer hook (old vs new comparison)",
307
+ )
308
+
309
+
310
+ def run_ruff_checks(files: List[Path]) -> ValidatorResult:
311
+ """Run ruff for fast Python linting."""
312
+ if not check_ruff_available():
313
+ return ValidatorResult(
314
+ name="Ruff",
315
+ checks="37",
316
+ passed=True,
317
+ output="Ruff not installed - skipping",
318
+ )
319
+
320
+ result = run_ruff_check(files)
321
+
322
+ return ValidatorResult(
323
+ name="Ruff",
324
+ checks="37",
325
+ passed=result.passed,
326
+ output=result.output,
327
+ )
328
+
329
+
330
+ def run_mypy_checks(files: List[Path]) -> ValidatorResult:
331
+ """Run mypy for static type checking."""
332
+ if not check_mypy_available():
333
+ return ValidatorResult(
334
+ name="Mypy",
335
+ checks="39,40",
336
+ passed=True,
337
+ output="Mypy not installed - skipping",
338
+ )
339
+
340
+ result = run_mypy_check(files)
341
+
342
+ return ValidatorResult(
343
+ name="Mypy",
344
+ checks="39,40",
345
+ passed=result.passed,
346
+ output=result.output[:500] if len(result.output) > 500 else result.output,
347
+ )
348
+
349
+
350
+ def run_abbreviation_checks(files: List[Path]) -> ValidatorResult:
351
+ """Run abbreviation checks on Python files."""
352
+ py_files = [f for f in files if f.suffix == ".py"]
353
+ if not py_files:
354
+ return ValidatorResult(
355
+ name="Abbreviations",
356
+ checks="5",
357
+ passed=True,
358
+ output="No Python files to check",
359
+ )
360
+
361
+ result = subprocess.run(
362
+ [sys.executable, str(VALIDATORS_DIR / "abbreviation_checks.py")]
363
+ + [str(f) for f in py_files],
364
+ capture_output=True,
365
+ text=True,
366
+ )
367
+
368
+ return ValidatorResult(
369
+ name="Abbreviations",
370
+ checks="5",
371
+ passed=result.returncode == 0,
372
+ output=result.stdout or "All checks passed",
373
+ )
374
+
375
+
376
+ def run_pr_reference_checks(files: List[Path]) -> ValidatorResult:
377
+ """Run PR reference checks on code files."""
378
+ code_files = [f for f in files if f.suffix in (".py", ".ts", ".tsx", ".js", ".jsx")]
379
+ if not code_files:
380
+ return ValidatorResult(
381
+ name="PR References",
382
+ checks="6",
383
+ passed=True,
384
+ output="No code files to check",
385
+ )
386
+
387
+ result = subprocess.run(
388
+ [sys.executable, str(VALIDATORS_DIR / "pr_reference_checks.py")]
389
+ + [str(f) for f in code_files],
390
+ capture_output=True,
391
+ text=True,
392
+ )
393
+
394
+ return ValidatorResult(
395
+ name="PR References",
396
+ checks="6",
397
+ passed=result.returncode == 0,
398
+ output=result.stdout or "All checks passed",
399
+ )
400
+
401
+
402
+ def run_magic_value_checks(files: List[Path]) -> ValidatorResult:
403
+ """Run magic value checks on Python files."""
404
+ py_files = [f for f in files if f.suffix == ".py"]
405
+ if not py_files:
406
+ return ValidatorResult(
407
+ name="Magic Values",
408
+ checks="7",
409
+ passed=True,
410
+ output="No Python files to check",
411
+ )
412
+
413
+ result = subprocess.run(
414
+ [sys.executable, str(VALIDATORS_DIR / "magic_value_checks.py")]
415
+ + [str(f) for f in py_files],
416
+ capture_output=True,
417
+ text=True,
418
+ )
419
+
420
+ return ValidatorResult(
421
+ name="Magic Values",
422
+ checks="7",
423
+ passed=result.returncode == 0,
424
+ output=result.stdout or "All checks passed",
425
+ )
426
+
427
+
428
+ def run_useless_test_checks(files: List[Path]) -> ValidatorResult:
429
+ """Run useless test checks on test files."""
430
+ test_files = [f for f in files if "test" in f.name.lower() and f.suffix == ".py"]
431
+ if not test_files:
432
+ return ValidatorResult(
433
+ name="Useless Tests",
434
+ checks="12",
435
+ passed=True,
436
+ output="No test files to check",
437
+ )
438
+
439
+ result = subprocess.run(
440
+ [sys.executable, str(VALIDATORS_DIR / "useless_test_checks.py")]
441
+ + [str(f) for f in test_files],
442
+ capture_output=True,
443
+ text=True,
444
+ )
445
+
446
+ return ValidatorResult(
447
+ name="Useless Tests",
448
+ checks="12",
449
+ passed=result.returncode == 0,
450
+ output=result.stdout or "All checks passed",
451
+ )
452
+
453
+
454
+ def run_security_checks(files: List[Path]) -> ValidatorResult:
455
+ """Run security checks on Python files."""
456
+ py_files = [f for f in files if f.suffix == ".py"]
457
+ if not py_files:
458
+ return ValidatorResult(
459
+ name="Security",
460
+ checks="27,28,29",
461
+ passed=True,
462
+ output="No Python files to check",
463
+ )
464
+
465
+ result = subprocess.run(
466
+ [sys.executable, str(VALIDATORS_DIR / "security_checks.py")]
467
+ + [str(f) for f in py_files],
468
+ capture_output=True,
469
+ text=True,
470
+ )
471
+
472
+ return ValidatorResult(
473
+ name="Security",
474
+ checks="27,28,29",
475
+ passed=result.returncode == 0,
476
+ output=result.stdout or "All checks passed",
477
+ )
478
+
479
+
480
+ def run_code_quality_checks(files: List[Path]) -> ValidatorResult:
481
+ """Run code quality checks on Python files."""
482
+ py_files = [f for f in files if f.suffix == ".py"]
483
+ if not py_files:
484
+ return ValidatorResult(
485
+ name="Code Quality",
486
+ checks="30,31,32",
487
+ passed=True,
488
+ output="No Python files to check",
489
+ )
490
+
491
+ result = subprocess.run(
492
+ [sys.executable, str(VALIDATORS_DIR / "code_quality_checks.py")]
493
+ + [str(f) for f in py_files],
494
+ capture_output=True,
495
+ text=True,
496
+ )
497
+
498
+ return ValidatorResult(
499
+ name="Code Quality",
500
+ checks="30,31,32",
501
+ passed=result.returncode == 0,
502
+ output=result.stdout or "All checks passed",
503
+ )
504
+
505
+
506
+ def run_python_antipattern_checks(files: List[Path]) -> ValidatorResult:
507
+ """Run Python anti-pattern checks on Python files."""
508
+ py_files = [f for f in files if f.suffix == ".py"]
509
+ if not py_files:
510
+ return ValidatorResult(
511
+ name="Python Anti-patterns",
512
+ checks="33,34,35",
513
+ passed=True,
514
+ output="No Python files to check",
515
+ )
516
+
517
+ result = subprocess.run(
518
+ [sys.executable, str(VALIDATORS_DIR / "python_antipattern_checks.py")]
519
+ + [str(f) for f in py_files],
520
+ capture_output=True,
521
+ text=True,
522
+ )
523
+
524
+ return ValidatorResult(
525
+ name="Python Anti-patterns",
526
+ checks="33,34,35",
527
+ passed=result.returncode == 0,
528
+ output=result.stdout or "All checks passed",
529
+ )
530
+
531
+
532
+ def run_todo_checks(files: List[Path]) -> ValidatorResult:
533
+ """Run TODO/FIXME checks on Python files."""
534
+ py_files = [f for f in files if f.suffix == ".py"]
535
+ if not py_files:
536
+ return ValidatorResult(
537
+ name="TODO Tracking",
538
+ checks="36",
539
+ passed=True,
540
+ output="No Python files to check",
541
+ )
542
+
543
+ result = subprocess.run(
544
+ [sys.executable, str(VALIDATORS_DIR / "todo_checks.py")]
545
+ + [str(f) for f in py_files],
546
+ capture_output=True,
547
+ text=True,
548
+ )
549
+
550
+ return ValidatorResult(
551
+ name="TODO Tracking",
552
+ checks="36",
553
+ passed=result.returncode == 0,
554
+ output=result.stdout or "All checks passed",
555
+ )
556
+
557
+
558
+ def run_type_safety_checks(files: List[Path]) -> ValidatorResult:
559
+ """Run type safety checks on Python files."""
560
+ py_files = [f for f in files if f.suffix == ".py"]
561
+ if not py_files:
562
+ return ValidatorResult(
563
+ name="Type Safety",
564
+ checks="39,40",
565
+ passed=True,
566
+ output="No Python files to check",
567
+ )
568
+
569
+ result = subprocess.run(
570
+ [sys.executable, str(VALIDATORS_DIR / "type_safety_checks.py")]
571
+ + [str(f) for f in py_files],
572
+ capture_output=True,
573
+ text=True,
574
+ )
575
+
576
+ return ValidatorResult(
577
+ name="Type Safety",
578
+ checks="39,40",
579
+ passed=result.returncode == 0,
580
+ output=result.stdout or "All checks passed",
581
+ )
582
+
583
+
584
+ def fix_python_style(files: List[Path]) -> List[str]:
585
+ """Apply Python style fixes to files.
586
+
587
+ Args:
588
+ files: List of files to fix
589
+
590
+ Returns:
591
+ List of files that were fixed
592
+ """
593
+ from python_style_checks import fix_file
594
+
595
+ fixed_files: List[str] = []
596
+ py_files = [f for f in files if f.suffix == ".py"]
597
+
598
+ for file_path in py_files:
599
+ if fix_file(file_path):
600
+ fixed_files.append(str(file_path))
601
+
602
+ return fixed_files
603
+
604
+
605
+ def get_changed_files() -> List[Path]:
606
+ """Get list of files changed in current commit/staging."""
607
+ # Try staged files first
608
+ result = subprocess.run(
609
+ ["git", "diff", "--cached", "--name-only"],
610
+ capture_output=True,
611
+ text=True,
612
+ )
613
+
614
+ files = result.stdout.strip().split("\n") if result.stdout.strip() else []
615
+
616
+ # If no staged files, try last commit
617
+ if not files:
618
+ result = subprocess.run(
619
+ ["git", "diff", "--name-only", "HEAD~1"],
620
+ capture_output=True,
621
+ text=True,
622
+ )
623
+ files = result.stdout.strip().split("\n") if result.stdout.strip() else []
624
+
625
+ return [Path(f) for f in files if f]
626
+
627
+
628
+ def main() -> int:
629
+ """Run all validators and report results."""
630
+ parser = argparse.ArgumentParser(description="Run pre-push validators")
631
+ parser.add_argument(
632
+ "--fix",
633
+ action="store_true",
634
+ help="Auto-fix violations where possible",
635
+ )
636
+ parser.add_argument(
637
+ "--health",
638
+ action="store_true",
639
+ help="Run health check only",
640
+ )
641
+ parser.add_argument(
642
+ "--json",
643
+ action="store_true",
644
+ help="Output results as JSON",
645
+ )
646
+ parser.add_argument(
647
+ "--no-color",
648
+ action="store_true",
649
+ help="Disable colored output",
650
+ )
651
+ parser.add_argument(
652
+ "--context",
653
+ type=int,
654
+ default=2,
655
+ help="Lines of context around violations",
656
+ )
657
+ args = parser.parse_args()
658
+
659
+ if args.health:
660
+ from health_check import get_system_health, print_health_report
661
+ health = get_system_health()
662
+ print_health_report(health)
663
+ return 0 if health.all_healthy else 1
664
+
665
+ mode = OutputMode.JSON if args.json else OutputMode.TEXT
666
+ formatter = OutputFormatter(
667
+ mode=mode,
668
+ use_colors=not args.no_color,
669
+ context_lines=args.context,
670
+ )
671
+
672
+ start_time = time.time()
673
+
674
+ if not args.json:
675
+ print(formatter.format_header("PRE-PUSH VALIDATOR RESULTS"))
676
+
677
+ files = get_changed_files()
678
+ if not files:
679
+ if not args.json:
680
+ print("No changed files detected. Skipping file-based checks.\n")
681
+ else:
682
+ if not args.json:
683
+ print(f"Checking {len(files)} changed file(s):")
684
+ for file_path in files[:10]:
685
+ print(f" - {file_path}")
686
+ if len(files) > 10:
687
+ print(f" ... and {len(files) - 10} more")
688
+ print()
689
+
690
+ if args.fix and files and not args.json:
691
+ print("Applying auto-fixes...")
692
+ fixed_files = fix_python_style(files)
693
+ if fixed_files:
694
+ print(f"Fixed {len(fixed_files)} file(s):")
695
+ for fixed_file in fixed_files:
696
+ print(f" - {fixed_file}")
697
+ print()
698
+ else:
699
+ print("No auto-fixes needed.")
700
+ print()
701
+
702
+ results: List[ValidatorResult] = []
703
+ validators: List[Tuple[str, Callable[[], ValidatorResult]]] = []
704
+
705
+ if files:
706
+ validators = [
707
+ ("Python Style", lambda: run_python_style_checks(files)),
708
+ ("Test Safety", lambda: run_test_safety_checks(files)),
709
+ ("React", lambda: run_react_checks(files)),
710
+ ("Comments", lambda: run_comment_checks(files)),
711
+ ("Ruff", lambda: run_ruff_checks(files)),
712
+ ("Mypy", lambda: run_mypy_checks(files)),
713
+ ("Abbreviations", lambda: run_abbreviation_checks(files)),
714
+ ("PR References", lambda: run_pr_reference_checks(files)),
715
+ ("Magic Values", lambda: run_magic_value_checks(files)),
716
+ ("Useless Tests", lambda: run_useless_test_checks(files)),
717
+ ("Security", lambda: run_security_checks(files)),
718
+ ("Code Quality", lambda: run_code_quality_checks(files)),
719
+ ("Python Anti-patterns", lambda: run_python_antipattern_checks(files)),
720
+ ("TODO Tracking", lambda: run_todo_checks(files)),
721
+ ("Type Safety", lambda: run_type_safety_checks(files)),
722
+ ]
723
+
724
+ validators.extend([
725
+ ("File Structure", run_file_structure_checks),
726
+ ("Git/PR", run_git_checks),
727
+ ])
728
+
729
+ for i, (name, validator_func) in enumerate(validators, 1):
730
+ if not args.json:
731
+ progress = formatter.format_progress(i, len(validators), name)
732
+ print(f"\r{progress}", end="", flush=True)
733
+
734
+ result = validator_func()
735
+ results.append(result)
736
+
737
+ if not args.json:
738
+ print("\r" + " " * 60 + "\r", end="")
739
+
740
+ all_passed = all(r.passed for r in results)
741
+ passed_count = sum(1 for r in results if r.passed)
742
+ failed_count = len(results) - passed_count
743
+
744
+ if args.json:
745
+ json_results: List[ValidatorResultDict] = [
746
+ {"name": r.name, "checks": r.checks, "passed": r.passed, "output": r.output}
747
+ for r in results
748
+ ]
749
+ print(formatter.format_results(json_results))
750
+ else:
751
+ for result in results:
752
+ print(formatter.format_result(result.name, result.checks, result.passed, result.output))
753
+ print()
754
+
755
+ elapsed = time.time() - start_time
756
+ print(formatter.format_stats(len(files), failed_count, elapsed))
757
+ print(formatter.format_summary(passed_count, failed_count))
758
+
759
+ print()
760
+ print("MANUAL CHECKS REQUIRED:")
761
+ print(" [ ] Constants near usage")
762
+ print(" [ ] Consistent terminology")
763
+ print(" [ ] Required vs optional params")
764
+ print(" [ ] Single responsibility")
765
+ print(" [ ] No over-engineering")
766
+ print()
767
+
768
+ return 0 if all_passed else 1
769
+
770
+
771
+ if __name__ == "__main__":
772
+ sys.exit(main())