claude-dev-env 1.25.2 → 1.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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} +154 -5
- package/hooks/blocking/test_code_rules_enforcer.py +61 -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 +183 -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 +1 -1
- 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 +1 -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} +26 -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 +56 -0
- 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 +45 -0
- package/hooks/notification/test_subagent_complete_notify.py +79 -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/{destructive-command-blocker.py → destructive_command_blocker.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/{tdd-enforcer.py → tdd_enforcer.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
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Unit tests for subagent-complete-notify Discord wiring."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import pathlib
|
|
5
|
+
import types
|
|
6
|
+
from unittest.mock import patch
|
|
7
|
+
|
|
8
|
+
HOOK_DIRECTORY = pathlib.Path(__file__).parent
|
|
9
|
+
MODULE_PATH = HOOK_DIRECTORY / "subagent_complete_notify.py"
|
|
10
|
+
|
|
11
|
+
FIXTURE_ACTIVITY_SECRET_ID = "fixture-activity-id-0003"
|
|
12
|
+
FIXTURE_TASK_DESCRIPTION = "subagent finished research task"
|
|
13
|
+
FIXTURE_PROJECT_NAME = "fixture-project"
|
|
14
|
+
NON_WINDOWS_NON_WSL_PLATFORM = "Darwin"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_hook_with_environment(
|
|
18
|
+
environment_overrides: dict[str, str],
|
|
19
|
+
) -> types.ModuleType:
|
|
20
|
+
module_specification = importlib.util.spec_from_file_location(
|
|
21
|
+
"subagent_complete_notify_under_test",
|
|
22
|
+
MODULE_PATH,
|
|
23
|
+
)
|
|
24
|
+
assert module_specification is not None
|
|
25
|
+
assert module_specification.loader is not None
|
|
26
|
+
module_under_test = importlib.util.module_from_spec(module_specification)
|
|
27
|
+
with patch.dict("os.environ", environment_overrides, clear=False):
|
|
28
|
+
module_specification.loader.exec_module(module_under_test)
|
|
29
|
+
return module_under_test
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_main_forwards_activity_secret_id_to_notify_discord() -> None:
|
|
33
|
+
module_under_test = load_hook_with_environment(
|
|
34
|
+
{"BWS_DISCORD_ACTIVITY_SECRET_ID": FIXTURE_ACTIVITY_SECRET_ID}
|
|
35
|
+
)
|
|
36
|
+
with (
|
|
37
|
+
patch.object(
|
|
38
|
+
module_under_test,
|
|
39
|
+
"get_task_info_from_stdin",
|
|
40
|
+
return_value=FIXTURE_TASK_DESCRIPTION,
|
|
41
|
+
),
|
|
42
|
+
patch.object(
|
|
43
|
+
module_under_test, "get_project_name", return_value=FIXTURE_PROJECT_NAME
|
|
44
|
+
),
|
|
45
|
+
patch.object(module_under_test, "notify_ntfy"),
|
|
46
|
+
patch.object(module_under_test, "notify_discord") as discord_spy,
|
|
47
|
+
patch.object(module_under_test, "is_wsl", return_value=False),
|
|
48
|
+
patch.object(module_under_test, "platform") as platform_stub,
|
|
49
|
+
):
|
|
50
|
+
platform_stub.system.return_value = NON_WINDOWS_NON_WSL_PLATFORM
|
|
51
|
+
module_under_test.main()
|
|
52
|
+
assert discord_spy.call_count == 1
|
|
53
|
+
call_kwargs = discord_spy.call_args.kwargs
|
|
54
|
+
assert call_kwargs["webhook_secret_id"] == FIXTURE_ACTIVITY_SECRET_ID
|
|
55
|
+
assert call_kwargs["title"] == FIXTURE_PROJECT_NAME
|
|
56
|
+
assert call_kwargs["message"] == FIXTURE_TASK_DESCRIPTION
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_notify_ntfy_skips_when_topic_unset() -> None:
|
|
60
|
+
module_under_test = load_hook_with_environment({"CLAUDE_NTFY_TOPIC": ""})
|
|
61
|
+
with patch.object(module_under_test.subprocess, "Popen") as popen_spy:
|
|
62
|
+
module_under_test.notify_ntfy(title="t", message="m")
|
|
63
|
+
assert popen_spy.call_count == 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_main_skips_notify_discord_when_task_description_is_empty() -> None:
|
|
67
|
+
module_under_test = load_hook_with_environment(
|
|
68
|
+
{"BWS_DISCORD_ACTIVITY_SECRET_ID": FIXTURE_ACTIVITY_SECRET_ID}
|
|
69
|
+
)
|
|
70
|
+
with (
|
|
71
|
+
patch.object(module_under_test, "get_task_info_from_stdin", return_value=""),
|
|
72
|
+
patch.object(
|
|
73
|
+
module_under_test, "get_project_name", return_value=FIXTURE_PROJECT_NAME
|
|
74
|
+
),
|
|
75
|
+
patch.object(module_under_test, "notify_ntfy"),
|
|
76
|
+
patch.object(module_under_test, "notify_discord") as discord_spy,
|
|
77
|
+
):
|
|
78
|
+
module_under_test.main()
|
|
79
|
+
assert discord_spy.call_count == 0
|
|
@@ -119,7 +119,11 @@ repos:
|
|
|
119
119
|
hooks:
|
|
120
120
|
- id: python-style-checks
|
|
121
121
|
name: Python Style Checks
|
|
122
|
-
entry: python hooks/validators/python_style_checks.py
|
|
122
|
+
entry: python packages/claude-dev-env/hooks/validators/python_style_checks.py
|
|
123
|
+
args: []
|
|
124
|
+
pass_filenames: true
|
|
125
|
+
# Invokes the script directly via its ``__main__`` block so the
|
|
126
|
+
# ``validators`` package qualifier does not need PYTHONPATH setup.
|
|
123
127
|
language: system
|
|
124
128
|
types: [python]
|
|
125
129
|
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Pytest fixture module ensuring validators directory is importable regardless of invocation cwd."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
VALIDATORS_DIRECTORY = Path(__file__).resolve().parent
|
|
8
|
+
|
|
9
|
+
if str(VALIDATORS_DIRECTORY) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(VALIDATORS_DIRECTORY))
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Single source of truth for CONFIG / TEST / HOOK-INFRASTRUCTURE /
|
|
4
4
|
WORKFLOW-REGISTRY / MIGRATION path pattern sets. Both Pre-Write
|
|
5
|
-
(``
|
|
5
|
+
(``code_rules_enforcer.py``) and pre-push (``magic_value_checks.py``)
|
|
6
6
|
scanners must short-circuit on the same file categories; drift between
|
|
7
7
|
the two produced the "inconsistent verdicts" bug this module prevents.
|
|
8
8
|
|
|
@@ -6,6 +6,8 @@ import sys
|
|
|
6
6
|
from dataclasses import dataclass
|
|
7
7
|
from typing import List
|
|
8
8
|
|
|
9
|
+
from .config import DEFAULT_BASE_BRANCH_WHEN_UNKNOWN
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
SUBPROCESS_TIMEOUT_SECONDS = 30
|
|
11
13
|
|
|
@@ -33,6 +35,83 @@ def get_current_branch() -> str:
|
|
|
33
35
|
return ""
|
|
34
36
|
|
|
35
37
|
|
|
38
|
+
def check_single_commit_when_pr_exists() -> List[Violation]:
|
|
39
|
+
"""
|
|
40
|
+
Check that a PR branch has exactly 1 commit ahead of its base.
|
|
41
|
+
|
|
42
|
+
Returns empty list if:
|
|
43
|
+
- No PR exists for current branch
|
|
44
|
+
- gh CLI or git is not available
|
|
45
|
+
- gh/git times out
|
|
46
|
+
- Branch is exactly 1 commit ahead of base
|
|
47
|
+
- git rev-list output is non-numeric
|
|
48
|
+
|
|
49
|
+
Returns violation if:
|
|
50
|
+
- Branch is 0 commits ahead of base
|
|
51
|
+
- Branch is more than 1 commit ahead of base
|
|
52
|
+
"""
|
|
53
|
+
branch = get_current_branch()
|
|
54
|
+
if not branch:
|
|
55
|
+
return []
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
pr_info = subprocess.run(
|
|
59
|
+
["gh", "pr", "list", "--head", branch, "--json", "baseRefName,number"],
|
|
60
|
+
capture_output=True,
|
|
61
|
+
text=True,
|
|
62
|
+
check=True,
|
|
63
|
+
timeout=SUBPROCESS_TIMEOUT_SECONDS,
|
|
64
|
+
)
|
|
65
|
+
except FileNotFoundError:
|
|
66
|
+
return []
|
|
67
|
+
except subprocess.CalledProcessError:
|
|
68
|
+
return []
|
|
69
|
+
except subprocess.TimeoutExpired:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
pr_data = json.loads(pr_info.stdout)
|
|
74
|
+
except json.JSONDecodeError:
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
if not pr_data:
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
base_branch = pr_data[0].get("baseRefName", DEFAULT_BASE_BRANCH_WHEN_UNKNOWN)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
rev_list = subprocess.run(
|
|
84
|
+
["git", "rev-list", "--count", f"{base_branch}..HEAD"],
|
|
85
|
+
capture_output=True,
|
|
86
|
+
text=True,
|
|
87
|
+
check=True,
|
|
88
|
+
timeout=SUBPROCESS_TIMEOUT_SECONDS,
|
|
89
|
+
)
|
|
90
|
+
except FileNotFoundError:
|
|
91
|
+
return []
|
|
92
|
+
except subprocess.CalledProcessError:
|
|
93
|
+
return []
|
|
94
|
+
except subprocess.TimeoutExpired:
|
|
95
|
+
return []
|
|
96
|
+
|
|
97
|
+
commit_count_text = rev_list.stdout.strip()
|
|
98
|
+
try:
|
|
99
|
+
commit_count = int(commit_count_text)
|
|
100
|
+
except ValueError:
|
|
101
|
+
return []
|
|
102
|
+
|
|
103
|
+
if commit_count == 1:
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
return [
|
|
107
|
+
Violation(
|
|
108
|
+
file="",
|
|
109
|
+
line=0,
|
|
110
|
+
message=f"Branch must have exactly 1 commit ahead of {base_branch}, found {commit_count} commits",
|
|
111
|
+
)
|
|
112
|
+
]
|
|
113
|
+
|
|
114
|
+
|
|
36
115
|
def check_draft_pr_state() -> List[Violation]:
|
|
37
116
|
"""
|
|
38
117
|
Check that PR is in draft state.
|
|
@@ -93,6 +172,7 @@ def main() -> None:
|
|
|
93
172
|
"""Run all git checks and exit with appropriate code."""
|
|
94
173
|
violations: List[Violation] = []
|
|
95
174
|
|
|
175
|
+
violations.extend(check_single_commit_when_pr_exists())
|
|
96
176
|
violations.extend(check_draft_pr_state())
|
|
97
177
|
|
|
98
178
|
if violations:
|
|
@@ -12,11 +12,11 @@ import sys
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Dict, FrozenSet, List, Set, Tuple, Type
|
|
14
14
|
|
|
15
|
-
from exempt_paths import (
|
|
15
|
+
from .exempt_paths import (
|
|
16
16
|
is_config_file,
|
|
17
17
|
is_test_file,
|
|
18
18
|
)
|
|
19
|
-
from validator_base import Violation
|
|
19
|
+
from .validator_base import Violation
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
ALLOWED_NUMBERS: FrozenSet[int] = frozenset({-1, 0, 1})
|
|
@@ -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 = '''
|