claude-dev-env 1.25.1 → 1.26.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.
- package/CLAUDE.md +6 -0
- package/agents/clean-coder.md +1 -1
- package/docs/CODE_RULES.md +3 -1
- package/hooks/HOOK_SPECS_PROMPT_WORKFLOW.md +54 -0
- package/hooks/blocking/{code-rules-enforcer.py → code_rules_enforcer.py} +150 -5
- package/hooks/blocking/{destructive-command-blocker.py → destructive_command_blocker.py} +12 -4
- package/hooks/blocking/{tdd-enforcer.py → tdd_enforcer.py} +12 -0
- package/hooks/blocking/test_code_rules_enforcer_any_type_ignore.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_conftest_anchor.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dot_test_pattern.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_file_global_constants.py +181 -0
- package/hooks/blocking/test_code_rules_enforcer_fstring_scan.py +4 -4
- package/hooks/blocking/test_code_rules_enforcer_logger_fstring.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_magic_allowlist.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_magic_string_masking.py +104 -0
- package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_type_checking_scope.py +2 -2
- package/hooks/blocking/test_content_search_to_zoekt_redirector_integration.py +1 -1
- package/hooks/blocking/test_destructive_command_blocker.py +63 -4
- package/hooks/blocking/test_gh_body_arg_blocker.py +1 -1
- package/hooks/blocking/test_pr_description_enforcer.py +8 -8
- package/hooks/blocking/test_tdd_enforcer.py +53 -1
- package/hooks/github-action/pre-push-review.yml +27 -0
- package/hooks/hooks.json +28 -28
- package/hooks/lifecycle/{config-change-guard.py → config_change_guard.py} +27 -12
- package/hooks/lifecycle/test_config_change_guard.py +3 -3
- package/hooks/notification/{attention-needed-notify.py → attention_needed_notify.py} +7 -0
- package/hooks/notification/{claude-notification-handler.py → claude_notification_handler.py} +8 -0
- package/hooks/notification/notification_utils.py +60 -2
- package/hooks/notification/subagent_complete_notify.py +381 -0
- package/hooks/notification/test_attention_needed_notify.py +47 -0
- package/hooks/notification/test_claude_notification_handler.py +54 -0
- package/hooks/notification/test_notification_utils.py +91 -0
- package/hooks/notification/test_subagent_complete_notify.py +72 -0
- package/hooks/validators/README.md +5 -1
- package/hooks/validators/abbreviation_checks.py +1 -1
- package/hooks/validators/code_quality_checks.py +1 -1
- package/hooks/validators/config.py +5 -0
- package/hooks/validators/conftest.py +10 -0
- package/hooks/validators/exempt_paths.py +1 -1
- package/hooks/validators/git_checks.py +80 -0
- package/hooks/validators/magic_value_checks.py +2 -2
- package/hooks/validators/pr_reference_checks.py +1 -1
- package/hooks/validators/python_antipattern_checks.py +1 -1
- package/hooks/validators/run_all_validators.py +53 -105
- package/hooks/validators/security_checks.py +1 -1
- package/hooks/validators/test_abbreviation_checks.py +2 -2
- package/hooks/validators/test_code_quality_checks.py +2 -2
- package/hooks/validators/test_file_structure_checks.py +1 -1
- package/hooks/validators/test_git_checks.py +79 -13
- package/hooks/validators/test_health_check.py +1 -1
- package/hooks/validators/test_magic_value_checks.py +2 -2
- package/hooks/validators/test_mypy_integration.py +1 -1
- package/hooks/validators/test_output_formatter.py +3 -1
- package/hooks/validators/test_pr_reference_checks.py +2 -2
- package/hooks/validators/test_python_antipattern_checks.py +2 -2
- package/hooks/validators/test_python_style_checks.py +2 -4
- package/hooks/validators/test_react_checks.py +1 -1
- package/hooks/validators/test_ruff_integration.py +1 -1
- package/hooks/validators/test_run_all_validators.py +75 -43
- package/hooks/validators/test_run_all_validators_integration.py +14 -37
- package/hooks/validators/test_security_checks.py +2 -2
- package/hooks/validators/test_test_safety_checks.py +1 -1
- package/hooks/validators/test_todo_checks.py +2 -2
- package/hooks/validators/test_type_safety_checks.py +2 -2
- package/hooks/validators/test_useless_test_checks.py +2 -2
- package/hooks/validators/test_validator_base.py +1 -1
- package/hooks/validators/test_verify_paths.py +2 -4
- package/hooks/validators/todo_checks.py +1 -1
- package/hooks/validators/type_safety_checks.py +1 -1
- package/hooks/validators/useless_test_checks.py +1 -1
- package/package.json +1 -1
- package/rules/file-global-constants.md +71 -0
- package/rules/gh-body-file.md +1 -1
- package/rules/prompt-workflow-context-controls.md +48 -0
- package/scripts/sync_to_cursor/rules.py +2 -2
- package/scripts/tests/test_sync_to_cursor.py +2 -2
- package/skills/bugteam/CONSTRAINTS.md +37 -0
- package/skills/bugteam/EXAMPLES.md +64 -0
- package/skills/bugteam/PROMPTS.md +175 -0
- package/skills/bugteam/SKILL.md +204 -295
- package/skills/bugteam/SKILL_EVALS.md +346 -0
- package/skills/bugteam/scripts/README.md +37 -0
- package/skills/bugteam/scripts/bugteam_code_rules_gate.py +334 -0
- package/skills/bugteam/scripts/bugteam_preflight.py +135 -0
- package/skills/rule-audit/SKILL.md +4 -4
- /package/hooks/advisory/{migration-safety-advisor.py → migration_safety_advisor.py} +0 -0
- /package/hooks/advisory/{refactor-guard.py → refactor_guard.py} +0 -0
- /package/hooks/blocking/{block-main-commit.py → block_main_commit.py} +0 -0
- /package/hooks/blocking/{content-search-to-zoekt-redirector.py → content_search_to_zoekt_redirector.py} +0 -0
- /package/hooks/blocking/{gh-body-arg-blocker.py → gh_body_arg_blocker.py} +0 -0
- /package/hooks/blocking/{hedging-language-blocker.py → hedging_language_blocker.py} +0 -0
- /package/hooks/blocking/{pr-description-enforcer.py → pr_description_enforcer.py} +0 -0
- /package/hooks/blocking/{sensitive-file-protector.py → sensitive_file_protector.py} +0 -0
- /package/hooks/blocking/{test-preflight-check.py → test_preflight_check.py} +0 -0
- /package/hooks/blocking/{write-existing-file-blocker.py → write_existing_file_blocker.py} +0 -0
- /package/hooks/git-hooks/{post-commit.py → post_commit.py} +0 -0
- /package/hooks/lifecycle/{session-end-cleanup.py → session_end_cleanup.py} +0 -0
- /package/hooks/{rewrite-plugin-paths.py → rewrite_plugin_paths.py} +0 -0
- /package/hooks/session/{plugin-data-dir-cleanup.py → plugin_data_dir_cleanup.py} +0 -0
- /package/hooks/validation/{hook-format-validator.py → hook_format_validator.py} +0 -0
- /package/hooks/workflow/{auto-formatter.py → auto_formatter.py} +0 -0
- /package/hooks/workflow/{investigation-tracker-reset.py → investigation_tracker_reset.py} +0 -0
- /package/scripts/{sync-to-cursor.py → sync_to_cursor.py} +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
This script orchestrates all automated validators and produces a unified report.
|
|
4
4
|
Exit code 0 = all checks pass, 1 = violations found.
|
|
5
5
|
"""
|
|
6
|
+
# pragma: no-tdd-gate
|
|
6
7
|
|
|
7
8
|
import argparse
|
|
8
9
|
import subprocess
|
|
@@ -13,13 +14,31 @@ from datetime import datetime
|
|
|
13
14
|
from pathlib import Path
|
|
14
15
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
|
15
16
|
|
|
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
|
|
17
|
+
from .health_check import get_system_health, get_validator_version, print_health_report
|
|
18
|
+
from .mypy_integration import check_mypy_available, run_mypy_check
|
|
19
|
+
from .output_formatter import OutputFormatter, OutputMode, ValidatorResultDict
|
|
20
|
+
from .python_style_checks import fix_file
|
|
21
|
+
from .ruff_integration import check_ruff_available, run_ruff_check
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
VALIDATORS_DIR = Path(__file__).parent
|
|
25
|
+
hooks_dir = VALIDATORS_DIR.parent
|
|
26
|
+
package_name = VALIDATORS_DIR.name
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def invoke_validator_module(module_stem: str, forwarded_file_paths: List[str]) -> subprocess.CompletedProcess[str]: # pragma: no-tdd-gate
|
|
30
|
+
"""Run a sibling validator as ``python -m validators.<module_stem>``.
|
|
31
|
+
|
|
32
|
+
The subprocess is launched with ``cwd`` set to the hooks directory so the
|
|
33
|
+
``validators`` package qualifier resolves without requiring PYTHONPATH.
|
|
34
|
+
"""
|
|
35
|
+
qualified_module = ".".join([package_name, module_stem])
|
|
36
|
+
return subprocess.run(
|
|
37
|
+
[sys.executable, "-m", qualified_module, *forwarded_file_paths],
|
|
38
|
+
capture_output=True,
|
|
39
|
+
text=True,
|
|
40
|
+
cwd=str(hooks_dir),
|
|
41
|
+
)
|
|
23
42
|
|
|
24
43
|
|
|
25
44
|
@dataclass(frozen=True)
|
|
@@ -169,18 +188,13 @@ def run_python_style_checks(files: List[Path]) -> ValidatorResult:
|
|
|
169
188
|
output="No Python files to check",
|
|
170
189
|
)
|
|
171
190
|
|
|
172
|
-
result =
|
|
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
|
-
)
|
|
191
|
+
result = invoke_validator_module("python_style_checks", [str(f) for f in py_files])
|
|
178
192
|
|
|
179
193
|
return ValidatorResult(
|
|
180
194
|
name="Python Style",
|
|
181
195
|
checks="1,2,3,4",
|
|
182
196
|
passed=result.returncode == 0,
|
|
183
|
-
output=result.stdout or "All checks passed",
|
|
197
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
184
198
|
)
|
|
185
199
|
|
|
186
200
|
|
|
@@ -195,18 +209,13 @@ def run_test_safety_checks(files: List[Path]) -> ValidatorResult:
|
|
|
195
209
|
output="No test files to check",
|
|
196
210
|
)
|
|
197
211
|
|
|
198
|
-
result =
|
|
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
|
-
)
|
|
212
|
+
result = invoke_validator_module("test_safety_checks", [str(f) for f in test_files])
|
|
204
213
|
|
|
205
214
|
return ValidatorResult(
|
|
206
215
|
name="Test Safety",
|
|
207
216
|
checks="11,21",
|
|
208
217
|
passed=result.returncode == 0,
|
|
209
|
-
output=result.stdout or "All checks passed",
|
|
218
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
210
219
|
)
|
|
211
220
|
|
|
212
221
|
|
|
@@ -235,17 +244,13 @@ def run_file_structure_checks(project_root: Optional[Path] = None) -> ValidatorR
|
|
|
235
244
|
output="Not in a git repository - skipping",
|
|
236
245
|
)
|
|
237
246
|
|
|
238
|
-
result =
|
|
239
|
-
[sys.executable, str(VALIDATORS_DIR / "file_structure_checks.py"), str(project_root)],
|
|
240
|
-
capture_output=True,
|
|
241
|
-
text=True,
|
|
242
|
-
)
|
|
247
|
+
result = invoke_validator_module("file_structure_checks", [str(project_root)])
|
|
243
248
|
|
|
244
249
|
return ValidatorResult(
|
|
245
250
|
name="File Structure",
|
|
246
251
|
checks="14,15",
|
|
247
252
|
passed=result.returncode == 0,
|
|
248
|
-
output=result.stdout or "All checks passed",
|
|
253
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
249
254
|
)
|
|
250
255
|
|
|
251
256
|
|
|
@@ -260,39 +265,30 @@ def run_react_checks(files: List[Path]) -> ValidatorResult:
|
|
|
260
265
|
output="No React files to check",
|
|
261
266
|
)
|
|
262
267
|
|
|
263
|
-
result =
|
|
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
|
-
)
|
|
268
|
+
result = invoke_validator_module("react_checks", [str(f) for f in react_files])
|
|
269
269
|
|
|
270
270
|
return ValidatorResult(
|
|
271
271
|
name="React",
|
|
272
272
|
checks="17",
|
|
273
273
|
passed=result.returncode == 0,
|
|
274
|
-
output=result.stdout or "All checks passed",
|
|
274
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
275
275
|
)
|
|
276
276
|
|
|
277
277
|
|
|
278
278
|
def run_git_checks() -> ValidatorResult:
|
|
279
279
|
"""Run git/GitHub checks."""
|
|
280
|
-
result =
|
|
281
|
-
[sys.executable, str(VALIDATORS_DIR / "git_checks.py")],
|
|
282
|
-
capture_output=True,
|
|
283
|
-
text=True,
|
|
284
|
-
)
|
|
280
|
+
result = invoke_validator_module("git_checks", [])
|
|
285
281
|
|
|
286
282
|
return ValidatorResult(
|
|
287
283
|
name="Git/PR Workflow",
|
|
288
284
|
checks="23,24",
|
|
289
285
|
passed=result.returncode == 0,
|
|
290
|
-
output=result.stdout or "All checks passed",
|
|
286
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
291
287
|
)
|
|
292
288
|
|
|
293
289
|
|
|
294
290
|
def run_comment_checks(files: List[Path]) -> ValidatorResult:
|
|
295
|
-
"""Comment preservation is enforced by
|
|
291
|
+
"""Comment preservation is enforced by code_rules_enforcer hook.
|
|
296
292
|
|
|
297
293
|
The hook compares old vs new content to block NEW comments and
|
|
298
294
|
block DELETION of existing comments. This standalone validator
|
|
@@ -303,7 +299,7 @@ def run_comment_checks(files: List[Path]) -> ValidatorResult:
|
|
|
303
299
|
name="No Comments",
|
|
304
300
|
checks="26",
|
|
305
301
|
passed=True,
|
|
306
|
-
output="Handled by
|
|
302
|
+
output="Handled by code_rules_enforcer hook (old vs new comparison)",
|
|
307
303
|
)
|
|
308
304
|
|
|
309
305
|
|
|
@@ -358,18 +354,13 @@ def run_abbreviation_checks(files: List[Path]) -> ValidatorResult:
|
|
|
358
354
|
output="No Python files to check",
|
|
359
355
|
)
|
|
360
356
|
|
|
361
|
-
result =
|
|
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
|
-
)
|
|
357
|
+
result = invoke_validator_module("abbreviation_checks", [str(f) for f in py_files])
|
|
367
358
|
|
|
368
359
|
return ValidatorResult(
|
|
369
360
|
name="Abbreviations",
|
|
370
361
|
checks="5",
|
|
371
362
|
passed=result.returncode == 0,
|
|
372
|
-
output=result.stdout or "All checks passed",
|
|
363
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
373
364
|
)
|
|
374
365
|
|
|
375
366
|
|
|
@@ -384,18 +375,13 @@ def run_pr_reference_checks(files: List[Path]) -> ValidatorResult:
|
|
|
384
375
|
output="No code files to check",
|
|
385
376
|
)
|
|
386
377
|
|
|
387
|
-
result =
|
|
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
|
-
)
|
|
378
|
+
result = invoke_validator_module("pr_reference_checks", [str(f) for f in code_files])
|
|
393
379
|
|
|
394
380
|
return ValidatorResult(
|
|
395
381
|
name="PR References",
|
|
396
382
|
checks="6",
|
|
397
383
|
passed=result.returncode == 0,
|
|
398
|
-
output=result.stdout or "All checks passed",
|
|
384
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
399
385
|
)
|
|
400
386
|
|
|
401
387
|
|
|
@@ -410,18 +396,13 @@ def run_magic_value_checks(files: List[Path]) -> ValidatorResult:
|
|
|
410
396
|
output="No Python files to check",
|
|
411
397
|
)
|
|
412
398
|
|
|
413
|
-
result =
|
|
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
|
-
)
|
|
399
|
+
result = invoke_validator_module("magic_value_checks", [str(f) for f in py_files])
|
|
419
400
|
|
|
420
401
|
return ValidatorResult(
|
|
421
402
|
name="Magic Values",
|
|
422
403
|
checks="7",
|
|
423
404
|
passed=result.returncode == 0,
|
|
424
|
-
output=result.stdout or "All checks passed",
|
|
405
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
425
406
|
)
|
|
426
407
|
|
|
427
408
|
|
|
@@ -436,18 +417,13 @@ def run_useless_test_checks(files: List[Path]) -> ValidatorResult:
|
|
|
436
417
|
output="No test files to check",
|
|
437
418
|
)
|
|
438
419
|
|
|
439
|
-
result =
|
|
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
|
-
)
|
|
420
|
+
result = invoke_validator_module("useless_test_checks", [str(f) for f in test_files])
|
|
445
421
|
|
|
446
422
|
return ValidatorResult(
|
|
447
423
|
name="Useless Tests",
|
|
448
424
|
checks="12",
|
|
449
425
|
passed=result.returncode == 0,
|
|
450
|
-
output=result.stdout or "All checks passed",
|
|
426
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
451
427
|
)
|
|
452
428
|
|
|
453
429
|
|
|
@@ -462,18 +438,13 @@ def run_security_checks(files: List[Path]) -> ValidatorResult:
|
|
|
462
438
|
output="No Python files to check",
|
|
463
439
|
)
|
|
464
440
|
|
|
465
|
-
result =
|
|
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
|
-
)
|
|
441
|
+
result = invoke_validator_module("security_checks", [str(f) for f in py_files])
|
|
471
442
|
|
|
472
443
|
return ValidatorResult(
|
|
473
444
|
name="Security",
|
|
474
445
|
checks="27,28,29",
|
|
475
446
|
passed=result.returncode == 0,
|
|
476
|
-
output=result.stdout or "All checks passed",
|
|
447
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
477
448
|
)
|
|
478
449
|
|
|
479
450
|
|
|
@@ -488,18 +459,13 @@ def run_code_quality_checks(files: List[Path]) -> ValidatorResult:
|
|
|
488
459
|
output="No Python files to check",
|
|
489
460
|
)
|
|
490
461
|
|
|
491
|
-
result =
|
|
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
|
-
)
|
|
462
|
+
result = invoke_validator_module("code_quality_checks", [str(f) for f in py_files])
|
|
497
463
|
|
|
498
464
|
return ValidatorResult(
|
|
499
465
|
name="Code Quality",
|
|
500
466
|
checks="30,31,32",
|
|
501
467
|
passed=result.returncode == 0,
|
|
502
|
-
output=result.stdout or "All checks passed",
|
|
468
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
503
469
|
)
|
|
504
470
|
|
|
505
471
|
|
|
@@ -514,18 +480,13 @@ def run_python_antipattern_checks(files: List[Path]) -> ValidatorResult:
|
|
|
514
480
|
output="No Python files to check",
|
|
515
481
|
)
|
|
516
482
|
|
|
517
|
-
result =
|
|
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
|
-
)
|
|
483
|
+
result = invoke_validator_module("python_antipattern_checks", [str(f) for f in py_files])
|
|
523
484
|
|
|
524
485
|
return ValidatorResult(
|
|
525
486
|
name="Python Anti-patterns",
|
|
526
487
|
checks="33,34,35",
|
|
527
488
|
passed=result.returncode == 0,
|
|
528
|
-
output=result.stdout or "All checks passed",
|
|
489
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
529
490
|
)
|
|
530
491
|
|
|
531
492
|
|
|
@@ -540,18 +501,13 @@ def run_todo_checks(files: List[Path]) -> ValidatorResult:
|
|
|
540
501
|
output="No Python files to check",
|
|
541
502
|
)
|
|
542
503
|
|
|
543
|
-
result =
|
|
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
|
-
)
|
|
504
|
+
result = invoke_validator_module("todo_checks", [str(f) for f in py_files])
|
|
549
505
|
|
|
550
506
|
return ValidatorResult(
|
|
551
507
|
name="TODO Tracking",
|
|
552
508
|
checks="36",
|
|
553
509
|
passed=result.returncode == 0,
|
|
554
|
-
output=result.stdout or "All checks passed",
|
|
510
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
555
511
|
)
|
|
556
512
|
|
|
557
513
|
|
|
@@ -566,18 +522,13 @@ def run_type_safety_checks(files: List[Path]) -> ValidatorResult:
|
|
|
566
522
|
output="No Python files to check",
|
|
567
523
|
)
|
|
568
524
|
|
|
569
|
-
result =
|
|
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
|
-
)
|
|
525
|
+
result = invoke_validator_module("type_safety_checks", [str(f) for f in py_files])
|
|
575
526
|
|
|
576
527
|
return ValidatorResult(
|
|
577
528
|
name="Type Safety",
|
|
578
529
|
checks="39,40",
|
|
579
530
|
passed=result.returncode == 0,
|
|
580
|
-
output=result.stdout or "All checks passed",
|
|
531
|
+
output=result.stdout or result.stderr or "All checks passed",
|
|
581
532
|
)
|
|
582
533
|
|
|
583
534
|
|
|
@@ -590,8 +541,6 @@ def fix_python_style(files: List[Path]) -> List[str]:
|
|
|
590
541
|
Returns:
|
|
591
542
|
List of files that were fixed
|
|
592
543
|
"""
|
|
593
|
-
from python_style_checks import fix_file
|
|
594
|
-
|
|
595
544
|
fixed_files: List[str] = []
|
|
596
545
|
py_files = [f for f in files if f.suffix == ".py"]
|
|
597
546
|
|
|
@@ -657,7 +606,6 @@ def main() -> int:
|
|
|
657
606
|
args = parser.parse_args()
|
|
658
607
|
|
|
659
608
|
if args.health:
|
|
660
|
-
from health_check import get_system_health, print_health_report
|
|
661
609
|
health = get_system_health()
|
|
662
610
|
print_health_report(health)
|
|
663
611
|
return 0 if health.all_healthy else 1
|
|
@@ -5,11 +5,11 @@ from pathlib import Path
|
|
|
5
5
|
|
|
6
6
|
import pytest
|
|
7
7
|
|
|
8
|
-
from abbreviation_checks import (
|
|
8
|
+
from .abbreviation_checks import (
|
|
9
9
|
check_single_letter_variables,
|
|
10
10
|
validate_file,
|
|
11
11
|
)
|
|
12
|
-
from validator_base import Violation
|
|
12
|
+
from .validator_base import Violation
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
GOOD_DESCRIPTIVE_NAMES = '''
|
|
@@ -6,12 +6,12 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from code_quality_checks import (
|
|
9
|
+
from .code_quality_checks import (
|
|
10
10
|
check_function_length,
|
|
11
11
|
check_nesting_depth,
|
|
12
12
|
check_file_length,
|
|
13
13
|
)
|
|
14
|
-
from validator_base import Violation
|
|
14
|
+
from .validator_base import Violation
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
GOOD_SHORT_FUNCTION = '''
|
|
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
|
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
|
-
from git_checks import (
|
|
11
|
+
from .git_checks import (
|
|
12
12
|
Violation,
|
|
13
13
|
check_single_commit_when_pr_exists,
|
|
14
14
|
check_draft_pr_state,
|
|
@@ -22,7 +22,10 @@ class TestSingleCommitWhenPrExists:
|
|
|
22
22
|
@patch("git_checks.subprocess.run")
|
|
23
23
|
def test_no_pr_returns_empty(self, mock_run: MagicMock) -> None:
|
|
24
24
|
"""When no PR exists, check should return empty list."""
|
|
25
|
-
mock_run.
|
|
25
|
+
mock_run.side_effect = [
|
|
26
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
27
|
+
MagicMock(returncode=0, stdout="[]", stderr=""),
|
|
28
|
+
]
|
|
26
29
|
|
|
27
30
|
violations = check_single_commit_when_pr_exists()
|
|
28
31
|
|
|
@@ -32,6 +35,7 @@ class TestSingleCommitWhenPrExists:
|
|
|
32
35
|
def test_single_commit_ahead_passes(self, mock_run: MagicMock) -> None:
|
|
33
36
|
"""Exactly 1 commit ahead should pass."""
|
|
34
37
|
mock_run.side_effect = [
|
|
38
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
35
39
|
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
36
40
|
MagicMock(returncode=0, stdout="1", stderr=""),
|
|
37
41
|
]
|
|
@@ -44,6 +48,7 @@ class TestSingleCommitWhenPrExists:
|
|
|
44
48
|
def test_zero_commits_ahead_fails(self, mock_run: MagicMock) -> None:
|
|
45
49
|
"""Zero commits ahead should fail."""
|
|
46
50
|
mock_run.side_effect = [
|
|
51
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
47
52
|
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
48
53
|
MagicMock(returncode=0, stdout="0", stderr=""),
|
|
49
54
|
]
|
|
@@ -60,6 +65,7 @@ class TestSingleCommitWhenPrExists:
|
|
|
60
65
|
def test_multiple_commits_ahead_fails(self, mock_run: MagicMock) -> None:
|
|
61
66
|
"""More than 1 commit ahead should fail."""
|
|
62
67
|
mock_run.side_effect = [
|
|
68
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
63
69
|
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
64
70
|
MagicMock(returncode=0, stdout="3", stderr=""),
|
|
65
71
|
]
|
|
@@ -73,7 +79,10 @@ class TestSingleCommitWhenPrExists:
|
|
|
73
79
|
@patch("git_checks.subprocess.run")
|
|
74
80
|
def test_gh_cli_not_available_returns_empty(self, mock_run: MagicMock) -> None:
|
|
75
81
|
"""When gh CLI not available, should return empty (warning, not failure)."""
|
|
76
|
-
mock_run.side_effect =
|
|
82
|
+
mock_run.side_effect = [
|
|
83
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
84
|
+
FileNotFoundError("gh not found"),
|
|
85
|
+
]
|
|
77
86
|
|
|
78
87
|
violations = check_single_commit_when_pr_exists()
|
|
79
88
|
|
|
@@ -83,6 +92,7 @@ class TestSingleCommitWhenPrExists:
|
|
|
83
92
|
def test_git_not_available_returns_empty(self, mock_run: MagicMock) -> None:
|
|
84
93
|
"""When git not available, should return empty."""
|
|
85
94
|
mock_run.side_effect = [
|
|
95
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
86
96
|
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
87
97
|
FileNotFoundError("git not found"),
|
|
88
98
|
]
|
|
@@ -93,8 +103,9 @@ class TestSingleCommitWhenPrExists:
|
|
|
93
103
|
|
|
94
104
|
@patch("git_checks.subprocess.run")
|
|
95
105
|
def test_extracts_base_branch_from_pr_info(self, mock_run: MagicMock) -> None:
|
|
96
|
-
"""Should extract base branch name from gh pr list JSON output."""
|
|
106
|
+
"""Should extract base branch name from gh pr list JSON output, falling back to main when absent."""
|
|
97
107
|
mock_run.side_effect = [
|
|
108
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
98
109
|
MagicMock(returncode=0, stdout='[{"baseRefName": "develop", "number": 123}]', stderr=""),
|
|
99
110
|
MagicMock(returncode=0, stdout="2", stderr=""),
|
|
100
111
|
]
|
|
@@ -110,10 +121,30 @@ class TestSingleCommitWhenPrExists:
|
|
|
110
121
|
timeout=30,
|
|
111
122
|
)
|
|
112
123
|
|
|
124
|
+
mock_run.reset_mock()
|
|
125
|
+
mock_run.side_effect = [
|
|
126
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
127
|
+
MagicMock(returncode=0, stdout='[{"number": 123}]', stderr=""),
|
|
128
|
+
MagicMock(returncode=0, stdout="2", stderr=""),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
fallback_violations = check_single_commit_when_pr_exists()
|
|
132
|
+
|
|
133
|
+
assert len(fallback_violations) == 1
|
|
134
|
+
assert "main" in fallback_violations[0].message
|
|
135
|
+
mock_run.assert_any_call(
|
|
136
|
+
["git", "rev-list", "--count", "main..HEAD"],
|
|
137
|
+
capture_output=True,
|
|
138
|
+
text=True,
|
|
139
|
+
check=True,
|
|
140
|
+
timeout=30,
|
|
141
|
+
)
|
|
142
|
+
|
|
113
143
|
@patch("git_checks.subprocess.run")
|
|
114
144
|
def test_non_numeric_commit_count_returns_empty(self, mock_run: MagicMock) -> None:
|
|
115
145
|
"""When git rev-list returns non-numeric output, should return empty."""
|
|
116
146
|
mock_run.side_effect = [
|
|
147
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
117
148
|
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
118
149
|
MagicMock(returncode=0, stdout="not a number\n", stderr=""),
|
|
119
150
|
]
|
|
@@ -125,7 +156,10 @@ class TestSingleCommitWhenPrExists:
|
|
|
125
156
|
@patch("git_checks.subprocess.run")
|
|
126
157
|
def test_gh_timeout_returns_empty(self, mock_run: MagicMock) -> None:
|
|
127
158
|
"""When gh CLI times out, should return empty (warning, not failure)."""
|
|
128
|
-
mock_run.side_effect =
|
|
159
|
+
mock_run.side_effect = [
|
|
160
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
161
|
+
subprocess.TimeoutExpired(cmd=["gh", "pr", "list"], timeout=30),
|
|
162
|
+
]
|
|
129
163
|
|
|
130
164
|
violations = check_single_commit_when_pr_exists()
|
|
131
165
|
|
|
@@ -135,6 +169,7 @@ class TestSingleCommitWhenPrExists:
|
|
|
135
169
|
def test_git_timeout_returns_empty(self, mock_run: MagicMock) -> None:
|
|
136
170
|
"""When git times out, should return empty (warning, not failure)."""
|
|
137
171
|
mock_run.side_effect = [
|
|
172
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
138
173
|
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
139
174
|
subprocess.TimeoutExpired(cmd=["git", "rev-list"], timeout=30),
|
|
140
175
|
]
|
|
@@ -143,6 +178,37 @@ class TestSingleCommitWhenPrExists:
|
|
|
143
178
|
|
|
144
179
|
assert violations == []
|
|
145
180
|
|
|
181
|
+
@patch("git_checks.subprocess.run")
|
|
182
|
+
def test_passes_resolved_branch_name_to_gh(self, mock_run: MagicMock) -> None:
|
|
183
|
+
"""gh pr list must receive the resolved branch name, never the literal 'HEAD'."""
|
|
184
|
+
mock_run.side_effect = [
|
|
185
|
+
MagicMock(returncode=0, stdout="feature/my-branch\n", stderr=""),
|
|
186
|
+
MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
|
|
187
|
+
MagicMock(returncode=0, stdout="1", stderr=""),
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
check_single_commit_when_pr_exists()
|
|
191
|
+
|
|
192
|
+
mock_run.assert_any_call(
|
|
193
|
+
["gh", "pr", "list", "--head", "feature/my-branch", "--json", "baseRefName,number"],
|
|
194
|
+
capture_output=True,
|
|
195
|
+
text=True,
|
|
196
|
+
check=True,
|
|
197
|
+
timeout=30,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
@patch("git_checks.subprocess.run")
|
|
201
|
+
def test_unresolved_branch_returns_empty(self, mock_run: MagicMock) -> None:
|
|
202
|
+
"""When current branch cannot be resolved, should return empty."""
|
|
203
|
+
mock_run.side_effect = [
|
|
204
|
+
MagicMock(returncode=0, stdout="\n", stderr=""),
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
violations = check_single_commit_when_pr_exists()
|
|
208
|
+
|
|
209
|
+
assert violations == []
|
|
210
|
+
mock_run.assert_called_once()
|
|
211
|
+
|
|
146
212
|
|
|
147
213
|
class TestDraftPrState:
|
|
148
214
|
"""Test that PR is in draft state when pushing review fixes."""
|
|
@@ -208,8 +274,8 @@ class TestDraftPrState:
|
|
|
208
274
|
class TestMain:
|
|
209
275
|
"""Test main function integration."""
|
|
210
276
|
|
|
211
|
-
@patch("git_checks.check_single_commit_when_pr_exists")
|
|
212
|
-
@patch("git_checks.check_draft_pr_state")
|
|
277
|
+
@patch("validators.git_checks.check_single_commit_when_pr_exists")
|
|
278
|
+
@patch("validators.git_checks.check_draft_pr_state")
|
|
213
279
|
def test_main_no_violations_exits_zero(
|
|
214
280
|
self,
|
|
215
281
|
mock_draft: MagicMock,
|
|
@@ -227,8 +293,8 @@ class TestMain:
|
|
|
227
293
|
captured = capsys.readouterr()
|
|
228
294
|
assert captured.out == ""
|
|
229
295
|
|
|
230
|
-
@patch("git_checks.check_single_commit_when_pr_exists")
|
|
231
|
-
@patch("git_checks.check_draft_pr_state")
|
|
296
|
+
@patch("validators.git_checks.check_single_commit_when_pr_exists")
|
|
297
|
+
@patch("validators.git_checks.check_draft_pr_state")
|
|
232
298
|
def test_main_with_violations_exits_one(
|
|
233
299
|
self,
|
|
234
300
|
mock_draft: MagicMock,
|
|
@@ -248,8 +314,8 @@ class TestMain:
|
|
|
248
314
|
captured = capsys.readouterr()
|
|
249
315
|
assert "Branch has 3 commits ahead" in captured.out
|
|
250
316
|
|
|
251
|
-
@patch("git_checks.check_single_commit_when_pr_exists")
|
|
252
|
-
@patch("git_checks.check_draft_pr_state")
|
|
317
|
+
@patch("validators.git_checks.check_single_commit_when_pr_exists")
|
|
318
|
+
@patch("validators.git_checks.check_draft_pr_state")
|
|
253
319
|
def test_main_prints_violations_without_file_line(
|
|
254
320
|
self,
|
|
255
321
|
mock_draft: MagicMock,
|
|
@@ -270,8 +336,8 @@ class TestMain:
|
|
|
270
336
|
assert captured.out == "PR must be in draft state\n"
|
|
271
337
|
assert ":0:" not in captured.out
|
|
272
338
|
|
|
273
|
-
@patch("git_checks.check_single_commit_when_pr_exists")
|
|
274
|
-
@patch("git_checks.check_draft_pr_state")
|
|
339
|
+
@patch("validators.git_checks.check_single_commit_when_pr_exists")
|
|
340
|
+
@patch("validators.git_checks.check_draft_pr_state")
|
|
275
341
|
def test_main_prints_all_violations(
|
|
276
342
|
self,
|
|
277
343
|
mock_draft: MagicMock,
|
|
@@ -6,11 +6,11 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
import pytest
|
|
8
8
|
|
|
9
|
-
from magic_value_checks import (
|
|
9
|
+
from .magic_value_checks import (
|
|
10
10
|
check_magic_values,
|
|
11
11
|
validate_file,
|
|
12
12
|
)
|
|
13
|
-
from validator_base import Violation
|
|
13
|
+
from .validator_base import Violation
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
MAGIC_NUMBER_SOURCE = "x = 42\n"
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from unittest.mock import patch
|
|
5
5
|
|
|
6
|
-
from mypy_integration import MypyResult, check_mypy_available, run_mypy_check
|
|
6
|
+
from .mypy_integration import MypyResult, check_mypy_available, run_mypy_check
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def test_mypy_result_dataclass() -> None:
|
|
@@ -4,11 +4,11 @@ from pathlib import Path
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from pr_reference_checks import (
|
|
7
|
+
from .pr_reference_checks import (
|
|
8
8
|
check_pr_references,
|
|
9
9
|
validate_file,
|
|
10
10
|
)
|
|
11
|
-
from validator_base import Violation
|
|
11
|
+
from .validator_base import Violation
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
GOOD_NO_REFERENCES = '''
|