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,182 @@
1
+ """File structure validation checks for pre-PR validation."""
2
+
3
+ import sys
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+
9
+ EXCLUDED_DIRS = {".venv", "venv", "node_modules", ".git"}
10
+
11
+ # Django requires empty __init__.py in these directories for module discovery
12
+ DJANGO_REQUIRED_INIT_DIRS = {
13
+ "migrations",
14
+ "management",
15
+ "commands",
16
+ "templatetags",
17
+ }
18
+
19
+ # Files that indicate a directory is a Django app
20
+ DJANGO_APP_INDICATORS = {
21
+ "apps.py",
22
+ "models.py",
23
+ "views.py",
24
+ "admin.py",
25
+ "settings.py",
26
+ "urls.py",
27
+ }
28
+
29
+
30
+ @dataclass(frozen=True)
31
+ class Violation:
32
+ """Represents a validation violation."""
33
+
34
+ file: str
35
+ line: int
36
+ message: str
37
+
38
+
39
+ def _normalize_path(path: Path, project_root: Path) -> str:
40
+ """
41
+ Convert Path to normalized string with forward slashes.
42
+
43
+ Args:
44
+ path: Path to normalize
45
+ project_root: Project root for relative path calculation
46
+
47
+ Returns:
48
+ Normalized path string with forward slashes
49
+ """
50
+ relative = path.relative_to(project_root)
51
+ return relative.as_posix()
52
+
53
+
54
+ def _should_exclude_path(path: Path, project_root: Path) -> bool:
55
+ """Check if path should be excluded from validation."""
56
+ try:
57
+ relative = path.relative_to(project_root)
58
+ return any(part in EXCLUDED_DIRS for part in relative.parts)
59
+ except ValueError:
60
+ return False
61
+
62
+
63
+ def check_multiple_requirements_txt(project_root: Path) -> List[Violation]:
64
+ """
65
+ Check for multiple requirements.txt files in project.
66
+
67
+ Args:
68
+ project_root: Root directory of the project
69
+
70
+ Returns:
71
+ List of violations found (empty if single or no requirements.txt)
72
+ """
73
+ requirements_files: List[Path] = []
74
+
75
+ for req_file in project_root.rglob("requirements.txt"):
76
+ if not _should_exclude_path(req_file, project_root):
77
+ requirements_files.append(req_file)
78
+
79
+ if len(requirements_files) <= 1:
80
+ return []
81
+
82
+ extra_files = [
83
+ _normalize_path(f, project_root)
84
+ for f in requirements_files
85
+ if f != project_root / "requirements.txt"
86
+ ]
87
+
88
+ message = (
89
+ f"Multiple requirements.txt files found. "
90
+ f"Project should have exactly one requirements.txt at root. "
91
+ f"Extra files: {', '.join(extra_files)}"
92
+ )
93
+
94
+ return [Violation(file="requirements.txt", line=1, message=message)]
95
+
96
+
97
+ def _is_django_required_init(init_file: Path) -> bool:
98
+ """Check if __init__.py is in a Django-required directory or Django app."""
99
+ parent = init_file.parent
100
+ parent_name = parent.name
101
+
102
+ # Check if in known Django-required directory
103
+ if parent_name in DJANGO_REQUIRED_INIT_DIRS:
104
+ return True
105
+
106
+ # Check if this is a Django app directory (contains apps.py, models.py, etc.)
107
+ for indicator in DJANGO_APP_INDICATORS:
108
+ if (parent / indicator).exists():
109
+ return True
110
+
111
+ return False
112
+
113
+
114
+ def check_empty_init_files(project_root: Path) -> List[Violation]:
115
+ """
116
+ Check for empty __init__.py files that serve no purpose.
117
+
118
+ Excludes Django-required directories (migrations, management, commands, templatetags)
119
+ which need empty __init__.py for module discovery.
120
+
121
+ Args:
122
+ project_root: Root directory of the project
123
+
124
+ Returns:
125
+ List of violations found (one per empty __init__.py)
126
+ """
127
+ violations: List[Violation] = []
128
+
129
+ for init_file in project_root.rglob("__init__.py"):
130
+ if _should_exclude_path(init_file, project_root):
131
+ continue
132
+
133
+ # Skip Django-required empty inits
134
+ if _is_django_required_init(init_file):
135
+ continue
136
+
137
+ content = init_file.read_text(encoding="utf-8")
138
+ if not content.strip():
139
+ relative_path = _normalize_path(init_file, project_root)
140
+ message = (
141
+ "Empty __init__.py file serves no purpose. "
142
+ "Either add exports/initialization or delete the file."
143
+ )
144
+ violations.append(Violation(file=relative_path, line=1, message=message))
145
+
146
+ return violations
147
+
148
+
149
+ def main(args: List[str]) -> None:
150
+ """
151
+ Run all file structure checks and print violations.
152
+
153
+ Args:
154
+ args: Command line arguments (expects project root path)
155
+
156
+ Exits:
157
+ 0 if all checks pass, 1 if violations found or invalid usage
158
+ """
159
+ if len(args) == 0:
160
+ print("Usage: python file_structure_checks.py <project_root>")
161
+ sys.exit(1)
162
+
163
+ project_root = Path(args[0])
164
+ if not project_root.exists():
165
+ print(f"Error: Project root does not exist: {project_root}")
166
+ sys.exit(1)
167
+
168
+ all_violations: List[Violation] = []
169
+
170
+ all_violations.extend(check_multiple_requirements_txt(project_root))
171
+ all_violations.extend(check_empty_init_files(project_root))
172
+
173
+ if all_violations:
174
+ for violation in all_violations:
175
+ print(f"{violation.file}:{violation.line}: {violation.message}")
176
+ sys.exit(1)
177
+
178
+ sys.exit(0)
179
+
180
+
181
+ if __name__ == "__main__":
182
+ main(sys.argv[1:])
@@ -0,0 +1,107 @@
1
+ """Git and GitHub validation checks for pre-push review."""
2
+
3
+ import json
4
+ import subprocess
5
+ import sys
6
+ from dataclasses import dataclass
7
+ from typing import List
8
+
9
+
10
+ SUBPROCESS_TIMEOUT_SECONDS = 30
11
+
12
+
13
+ @dataclass
14
+ class Violation:
15
+ """Represents a validation violation."""
16
+ file: str
17
+ line: int
18
+ message: str
19
+
20
+
21
+ def get_current_branch() -> str:
22
+ """Get the current git branch name."""
23
+ try:
24
+ result = subprocess.run(
25
+ ["git", "rev-parse", "--abbrev-ref", "HEAD"],
26
+ capture_output=True,
27
+ text=True,
28
+ check=True,
29
+ timeout=SUBPROCESS_TIMEOUT_SECONDS,
30
+ )
31
+ return result.stdout.strip()
32
+ except (FileNotFoundError, subprocess.CalledProcessError, subprocess.TimeoutExpired):
33
+ return ""
34
+
35
+
36
+ def check_draft_pr_state() -> List[Violation]:
37
+ """
38
+ Check that PR is in draft state.
39
+
40
+ Returns empty list if:
41
+ - No PR exists for current branch
42
+ - gh CLI not available
43
+ - PR is in draft state
44
+
45
+ Returns violation if:
46
+ - PR is not in draft state
47
+ """
48
+ branch = get_current_branch()
49
+ if not branch:
50
+ return []
51
+
52
+ try:
53
+ pr_info = subprocess.run(
54
+ ["gh", "pr", "list", "--head", branch, "--json", "number,isDraft"],
55
+ capture_output=True,
56
+ text=True,
57
+ check=True,
58
+ timeout=SUBPROCESS_TIMEOUT_SECONDS,
59
+ )
60
+ except FileNotFoundError:
61
+ return []
62
+ except subprocess.CalledProcessError:
63
+ return []
64
+ except subprocess.TimeoutExpired:
65
+ return []
66
+
67
+ if not pr_info.stdout.strip():
68
+ return []
69
+
70
+ try:
71
+ pr_data = json.loads(pr_info.stdout)
72
+ except json.JSONDecodeError:
73
+ return []
74
+
75
+ if not pr_data:
76
+ return []
77
+
78
+ is_draft = pr_data[0].get("isDraft", False)
79
+
80
+ if is_draft:
81
+ return []
82
+
83
+ return [
84
+ Violation(
85
+ file="",
86
+ line=0,
87
+ message="PR must be in draft state before pushing. Run: gh pr ready --undo",
88
+ )
89
+ ]
90
+
91
+
92
+ def main() -> None:
93
+ """Run all git checks and exit with appropriate code."""
94
+ violations: List[Violation] = []
95
+
96
+ violations.extend(check_draft_pr_state())
97
+
98
+ if violations:
99
+ for violation in violations:
100
+ print(violation.message)
101
+ sys.exit(1)
102
+
103
+ sys.exit(0)
104
+
105
+
106
+ if __name__ == "__main__":
107
+ main()
@@ -0,0 +1,214 @@
1
+ """Health checks for validator availability and status.
2
+
3
+ Provides:
4
+ - Validator file existence checks
5
+ - Dependency availability checks
6
+ - Version tracking
7
+ """
8
+
9
+ import hashlib
10
+ import subprocess
11
+ import sys
12
+ from dataclasses import dataclass
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Dict, Optional
16
+
17
+
18
+ VALIDATOR_FILES = [
19
+ "python_style_checks.py",
20
+ "test_safety_checks.py",
21
+ "file_structure_checks.py",
22
+ "react_checks.py",
23
+ "git_checks.py",
24
+ "comment_checks.py",
25
+ ]
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class ValidatorHealth:
30
+ """Health status of a single validator."""
31
+
32
+ name: str
33
+ healthy: bool
34
+ error: Optional[str] = None
35
+ last_modified: Optional[datetime] = None
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class SystemHealth:
40
+ """Overall system health status."""
41
+
42
+ all_healthy: bool
43
+ validators: Dict[str, ValidatorHealth]
44
+ python_version: str
45
+ optional_tools: Dict[str, bool]
46
+
47
+
48
+ def check_validator_exists(validator_path: Path) -> ValidatorHealth:
49
+ """Check if a validator file exists and is readable.
50
+
51
+ Args:
52
+ validator_path: Path to validator Python file
53
+
54
+ Returns:
55
+ ValidatorHealth with status
56
+ """
57
+ name = validator_path.stem
58
+
59
+ if not validator_path.exists():
60
+ return ValidatorHealth(
61
+ name=name,
62
+ healthy=False,
63
+ error=f"Validator not found: {validator_path}",
64
+ )
65
+
66
+ try:
67
+ validator_path.read_text(encoding="utf-8")
68
+ mtime = datetime.fromtimestamp(validator_path.stat().st_mtime)
69
+ return ValidatorHealth(
70
+ name=name,
71
+ healthy=True,
72
+ last_modified=mtime,
73
+ )
74
+ except (IOError, OSError, PermissionError) as error:
75
+ return ValidatorHealth(
76
+ name=name,
77
+ healthy=False,
78
+ error=f"Cannot read validator: {error}",
79
+ )
80
+
81
+
82
+ def check_all_validators(validators_dir: Path) -> Dict[str, ValidatorHealth]:
83
+ """Check health of all required validators.
84
+
85
+ Args:
86
+ validators_dir: Directory containing validator files
87
+
88
+ Returns:
89
+ Dict mapping validator names to health status
90
+ """
91
+ results: Dict[str, ValidatorHealth] = {}
92
+
93
+ for validator_file in VALIDATOR_FILES:
94
+ validator_path = validators_dir / validator_file
95
+ health = check_validator_exists(validator_path)
96
+ results[health.name] = health
97
+
98
+ return results
99
+
100
+
101
+ def check_optional_tool(tool_name: str) -> bool:
102
+ """Check if an optional tool is available.
103
+
104
+ Args:
105
+ tool_name: Name of tool to check (ruff, mypy, isort)
106
+
107
+ Returns:
108
+ True if tool is available
109
+ """
110
+ try:
111
+ result = subprocess.run(
112
+ [tool_name, "--version"],
113
+ capture_output=True,
114
+ text=True,
115
+ )
116
+ return result.returncode == 0
117
+ except FileNotFoundError:
118
+ return False
119
+
120
+
121
+ def get_validator_version(validators_dir: Optional[Path] = None) -> str:
122
+ """Get a version string for the validator suite.
123
+
124
+ Args:
125
+ validators_dir: Optional override for validators directory
126
+
127
+ Returns:
128
+ Version string based on file hashes
129
+ """
130
+ if validators_dir is None:
131
+ validators_dir = Path(__file__).parent
132
+ hasher = hashlib.md5()
133
+
134
+ for validator_file in sorted(VALIDATOR_FILES):
135
+ validator_path = validators_dir / validator_file
136
+ if validator_path.exists():
137
+ content = validator_path.read_bytes()
138
+ hasher.update(content)
139
+
140
+ return hasher.hexdigest()[:8]
141
+
142
+
143
+ def get_system_health(validators_dir: Optional[Path] = None) -> SystemHealth:
144
+ """Get complete system health status.
145
+
146
+ Args:
147
+ validators_dir: Optional override for validators directory
148
+
149
+ Returns:
150
+ SystemHealth with all status information
151
+ """
152
+ if validators_dir is None:
153
+ validators_dir = Path(__file__).parent
154
+
155
+ validators = check_all_validators(validators_dir)
156
+ all_healthy = all(v.healthy for v in validators.values())
157
+
158
+ optional_tools = {
159
+ "ruff": check_optional_tool("ruff"),
160
+ "mypy": check_optional_tool("mypy"),
161
+ "isort": check_optional_tool("isort"),
162
+ }
163
+
164
+ return SystemHealth(
165
+ all_healthy=all_healthy,
166
+ validators=validators,
167
+ python_version=sys.version,
168
+ optional_tools=optional_tools,
169
+ )
170
+
171
+
172
+ def print_health_report(health: SystemHealth) -> None:
173
+ """Print a formatted health report.
174
+
175
+ Args:
176
+ health: SystemHealth to report
177
+ """
178
+ print("=" * 60)
179
+ print("VALIDATOR HEALTH CHECK")
180
+ print("=" * 60)
181
+ print()
182
+
183
+ print(f"Python: {health.python_version.split()[0]}")
184
+ print(f"Version: {get_validator_version()}")
185
+ print()
186
+
187
+ print("Required Validators:")
188
+ for name, validator in sorted(health.validators.items()):
189
+ status = "[OK]" if validator.healthy else "[MISSING]"
190
+ print(f" {status} {name}")
191
+ if validator.error:
192
+ print(f" Error: {validator.error}")
193
+ print()
194
+
195
+ print("Optional Tools:")
196
+ for tool, available in sorted(health.optional_tools.items()):
197
+ status = "[OK]" if available else "[NOT INSTALLED]"
198
+ print(f" {status} {tool}")
199
+ print()
200
+
201
+ overall = "HEALTHY" if health.all_healthy else "DEGRADED"
202
+ print(f"Overall Status: {overall}")
203
+ print("=" * 60)
204
+
205
+
206
+ def main() -> int:
207
+ """Run health check and print report."""
208
+ health = get_system_health()
209
+ print_health_report(health)
210
+ return 0 if health.all_healthy else 1
211
+
212
+
213
+ if __name__ == "__main__":
214
+ sys.exit(main())
@@ -0,0 +1,81 @@
1
+ """Magic value detection validator.
2
+
3
+ Implements check 7: No hardcoded magic values.
4
+ Use named constants instead of magic numbers.
5
+
6
+ Note: Only checks for magic numbers. Magic string detection is not implemented.
7
+ """
8
+
9
+ import ast
10
+ import sys
11
+ from pathlib import Path
12
+ from typing import List, Set
13
+
14
+ from validator_base import Violation
15
+
16
+
17
+ ALLOWED_NUMBERS: Set[int] = frozenset({-1, 0, 1, 2, 100})
18
+
19
+
20
+ def check_magic_values(tree: ast.AST, filename: str) -> List[Violation]:
21
+ violations: List[Violation] = []
22
+
23
+ constant_names: Set[str] = set()
24
+ for node in ast.walk(tree):
25
+ if isinstance(node, ast.Assign):
26
+ for target in node.targets:
27
+ if isinstance(target, ast.Name) and target.id.isupper():
28
+ constant_names.add(target.id)
29
+
30
+ for node in ast.walk(tree):
31
+ if isinstance(node, ast.Constant):
32
+ if isinstance(node.value, int) and node.value not in ALLOWED_NUMBERS:
33
+ if not _is_in_constant_definition(node, tree):
34
+ violations.append(
35
+ Violation(
36
+ filename,
37
+ node.lineno,
38
+ f"Magic number {node.value} - use named constant",
39
+ )
40
+ )
41
+
42
+ return violations
43
+
44
+
45
+ def _is_in_constant_definition(node: ast.Constant, tree: ast.AST) -> bool:
46
+ for parent in ast.walk(tree):
47
+ if isinstance(parent, ast.Assign):
48
+ for target in parent.targets:
49
+ if isinstance(target, ast.Name) and target.id.isupper():
50
+ if parent.value is node:
51
+ return True
52
+ return False
53
+
54
+
55
+ def validate_file(file_path: Path) -> List[Violation]:
56
+ filename = str(file_path)
57
+ try:
58
+ source = file_path.read_text(encoding="utf-8")
59
+ tree = ast.parse(source)
60
+ except Exception as error:
61
+ return [Violation(filename, 0, f"Error: {error}")]
62
+
63
+ return check_magic_values(tree, filename)
64
+
65
+
66
+ def main() -> int:
67
+ if len(sys.argv) < 2:
68
+ return 1
69
+
70
+ all_violations: List[Violation] = []
71
+ for file_arg in sys.argv[1:]:
72
+ all_violations.extend(validate_file(Path(file_arg)))
73
+
74
+ for violation in all_violations:
75
+ print(violation)
76
+
77
+ return 1 if all_violations else 0
78
+
79
+
80
+ if __name__ == "__main__":
81
+ sys.exit(main())
@@ -0,0 +1,52 @@
1
+ """Mypy integration for static type checking."""
2
+
3
+ import subprocess
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+
8
+ @dataclass
9
+ class MypyResult:
10
+ passed: bool
11
+ output: str
12
+ error_count: int
13
+
14
+
15
+ def check_mypy_available() -> bool:
16
+ """Check if mypy is installed."""
17
+ try:
18
+ result = subprocess.run(
19
+ ["mypy", "--version"],
20
+ capture_output=True,
21
+ text=True,
22
+ )
23
+ return result.returncode == 0
24
+ except FileNotFoundError:
25
+ return False
26
+
27
+
28
+ def run_mypy_check(files: list[Path]) -> MypyResult:
29
+ """Run mypy on files."""
30
+ if not files:
31
+ return MypyResult(passed=True, output="No files to check", error_count=0)
32
+
33
+ if not check_mypy_available():
34
+ return MypyResult(passed=True, output="Mypy not installed - skipping", error_count=0)
35
+
36
+ py_files = [str(f) for f in files if f.suffix == ".py"]
37
+ if not py_files:
38
+ return MypyResult(passed=True, output="No Python files", error_count=0)
39
+
40
+ result = subprocess.run(
41
+ ["mypy", "--ignore-missing-imports", "--no-error-summary"] + py_files,
42
+ capture_output=True,
43
+ text=True,
44
+ )
45
+
46
+ error_count = result.stdout.count(": error:")
47
+
48
+ return MypyResult(
49
+ passed=result.returncode == 0,
50
+ output=result.stdout or result.stderr or "No type errors",
51
+ error_count=error_count,
52
+ )