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.
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/agents/agent-writer.md +157 -0
- package/agents/clasp-deployment-orchestrator.md +609 -0
- package/agents/clean-coder.md +295 -0
- package/agents/code-quality-agent.md +40 -0
- package/agents/code-standards-agent.md +93 -0
- package/agents/config-centralizer.md +686 -0
- package/agents/config-extraction-agent.md +225 -0
- package/agents/doc-orchestrator.md +47 -0
- package/agents/docs-agent.md +112 -0
- package/agents/docx-agent.md +211 -0
- package/agents/git-commit-crafter.md +100 -0
- package/agents/magic-value-eliminator-agent.md +72 -0
- package/agents/mandatory-agent-workflow-agent.md +88 -0
- package/agents/parallel-workflow-coordinator.md +779 -0
- package/agents/pdf-agent.md +302 -0
- package/agents/plan-executor.md +226 -0
- package/agents/pr-description-writer.md +87 -0
- package/agents/project-context-loader.md +238 -0
- package/agents/project-docs-analyzer.md +54 -0
- package/agents/project-structure-organizer-agent.md +72 -0
- package/agents/readability-review-agent.md +76 -0
- package/agents/refactoring-specialist.md +69 -0
- package/agents/right-sized-engineer.md +129 -0
- package/agents/session-continuity-manager.md +53 -0
- package/agents/skill-to-agent-converter.md +371 -0
- package/agents/skill-writer-agent.md +470 -0
- package/agents/stub-detector-agent.md +140 -0
- package/agents/tdd-test-writer.md +62 -0
- package/agents/test-data-builder.md +68 -0
- package/agents/tooling-builder.md +78 -0
- package/agents/user-docs-writer.md +67 -0
- package/agents/validation-expert.md +71 -0
- package/agents/workflow-visual-documenter.md +82 -0
- package/agents/xlsx-agent.md +169 -0
- package/bin/install.mjs +256 -0
- package/commands/commit.md +28 -0
- package/commands/docupdate.md +322 -0
- package/commands/implement.md +102 -0
- package/commands/initialize.md +91 -0
- package/commands/plan.md +63 -0
- package/commands/pr-comments.md +47 -0
- package/commands/readability-review.md +20 -0
- package/commands/review-plan.md +7 -0
- package/commands/right-size.md +15 -0
- package/commands/stubcheck.md +89 -0
- package/commands/sum.md +30 -0
- package/docs/CODE_RULES.md +186 -0
- package/docs/DJANGO_PATTERNS.md +80 -0
- package/docs/REACT_PATTERNS.md +185 -0
- package/docs/TEST_QUALITY.md +104 -0
- package/hooks/advisory/migration-safety-advisor.py +49 -0
- package/hooks/advisory/refactor-guard.py +205 -0
- package/hooks/blocking/block-main-commit.py +168 -0
- package/hooks/blocking/code-rules-enforcer.py +549 -0
- package/hooks/blocking/destructive-command-blocker.py +107 -0
- package/hooks/blocking/docker-settings-guard.py +44 -0
- package/hooks/blocking/hedging-language-blocker.py +130 -0
- package/hooks/blocking/parallel-task-blocker.py +69 -0
- package/hooks/blocking/pr-description-enforcer.py +87 -0
- package/hooks/blocking/pyautogui-scroll-blocker.py +74 -0
- package/hooks/blocking/sensitive-file-protector.py +70 -0
- package/hooks/blocking/tdd-enforcer.py +62 -0
- package/hooks/blocking/test-preflight-check.py +343 -0
- package/hooks/blocking/write-existing-file-blocker.py +63 -0
- package/hooks/git-hooks/post-commit.py +103 -0
- package/hooks/github-action/test_workflow.py +33 -0
- package/hooks/hooks.json +246 -0
- package/hooks/lifecycle/config-change-guard.py +84 -0
- package/hooks/lifecycle/session-end-cleanup.py +59 -0
- package/hooks/notification/attention-needed-notify.py +63 -0
- package/hooks/notification/claude-notification-handler.py +59 -0
- package/hooks/notification/notification_utils.py +206 -0
- package/hooks/rewrite-plugin-paths.py +116 -0
- package/hooks/session/bulk-edit-reminder.py +30 -0
- package/hooks/session/code-rules-reminder.py +97 -0
- package/hooks/session/compact-context-reinject.py +39 -0
- package/hooks/session/hook-structure-context.py +140 -0
- package/hooks/session/plugin-data-dir-cleanup.py +39 -0
- package/hooks/validation/code-style-validator.py +145 -0
- package/hooks/validation/e2e-test-validator.py +142 -0
- package/hooks/validation/hook-format-validator.py +66 -0
- package/hooks/validation/mypy_validator.py +180 -0
- package/hooks/validators/README.md +125 -0
- package/hooks/validators/VALIDATION_REPORT.md +287 -0
- package/hooks/validators/__init__.py +19 -0
- package/hooks/validators/abbreviation_checks.py +82 -0
- package/hooks/validators/code_quality_checks.py +133 -0
- package/hooks/validators/comment_checks.py +188 -0
- package/hooks/validators/file_structure_checks.py +182 -0
- package/hooks/validators/git_checks.py +107 -0
- package/hooks/validators/health_check.py +214 -0
- package/hooks/validators/magic_value_checks.py +81 -0
- package/hooks/validators/mypy_integration.py +52 -0
- package/hooks/validators/output_formatter.py +266 -0
- package/hooks/validators/pr_reference_checks.py +72 -0
- package/hooks/validators/python_antipattern_checks.py +110 -0
- package/hooks/validators/python_style_checks.py +364 -0
- package/hooks/validators/react_checks.py +90 -0
- package/hooks/validators/ruff_integration.py +80 -0
- package/hooks/validators/run_all_validators.py +772 -0
- package/hooks/validators/security_checks.py +135 -0
- package/hooks/validators/test_abbreviation_checks.py +76 -0
- package/hooks/validators/test_bad.tsx +7 -0
- package/hooks/validators/test_code_quality_checks.py +129 -0
- package/hooks/validators/test_file_structure_checks.py +307 -0
- package/hooks/validators/test_files/01_basic_component.tsx +10 -0
- package/hooks/validators/test_files/02_component_without_react.tsx +10 -0
- package/hooks/validators/test_files/03_pure_component.tsx +10 -0
- package/hooks/validators/test_files/04_pure_component_import.tsx +10 -0
- package/hooks/validators/test_files/05_typescript_generics.tsx +14 -0
- package/hooks/validators/test_files/06_typescript_two_generics.tsx +18 -0
- package/hooks/validators/test_files/07_multiline_declaration.tsx +11 -0
- package/hooks/validators/test_files/08_error_boundary_valid.tsx +14 -0
- package/hooks/validators/test_files/09_error_boundary_with_other_class.tsx +20 -0
- package/hooks/validators/test_files/10_inheritance_chain.tsx +16 -0
- package/hooks/validators/test_files/11_ts_file.ts +10 -0
- package/hooks/validators/test_files/12_non_react_class.tsx +14 -0
- package/hooks/validators/test_files/13_functional_component.tsx +8 -0
- package/hooks/validators/test_files/14_indented_class.tsx +13 -0
- package/hooks/validators/test_files/15_getDerivedStateFromError.tsx +14 -0
- package/hooks/validators/test_files/16_mixed_components.tsx +20 -0
- package/hooks/validators/test_files/EXECUTIVE_SUMMARY.md +175 -0
- package/hooks/validators/test_files/TEST_RESULTS_TABLE.txt +60 -0
- package/hooks/validators/test_files/VALIDATION_REPORT.md +201 -0
- package/hooks/validators/test_files/async_views.py +23 -0
- package/hooks/validators/test_files/async_with_imports.py +14 -0
- package/hooks/validators/test_files/bad_inline_imports.py +37 -0
- package/hooks/validators/test_files/management/commands/cmd_01_no_debug_check.py +10 -0
- package/hooks/validators/test_files/management/commands/cmd_02_proper_debug_check.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_03_debug_check_with_return.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_04_imported_DEBUG.py +14 -0
- package/hooks/validators/test_files/management/commands/cmd_05_debug_check_in_helper.py +16 -0
- package/hooks/validators/test_files/management/commands/cmd_06_debug_check_late.py +22 -0
- package/hooks/validators/test_files/management/commands/cmd_07_positive_debug_check.py +15 -0
- package/hooks/validators/test_files/management/commands/cmd_08_debug_with_and.py +14 -0
- package/hooks/validators/test_files/not_management_command.py +10 -0
- package/hooks/validators/test_files/skip_decorators/test_01_simple_skip.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_02_pytest_skipif.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_03_unittest_skipIf.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_04_skip_with_parens.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_05_xfail.py +7 -0
- package/hooks/validators/test_files/skip_decorators/test_06_custom_skip.py +11 -0
- package/hooks/validators/test_files/skip_decorators/test_07_capital_Skip.py +8 -0
- package/hooks/validators/test_files/skip_decorators/test_08_skipUnless.py +7 -0
- package/hooks/validators/test_files/skip_decorators/test_09_pytest_mark_skip_simple.py +7 -0
- package/hooks/validators/test_files/test_async_functions.py +45 -0
- package/hooks/validators/test_files/test_purecomponent/PureComponentExample.tsx +7 -0
- package/hooks/validators/test_files/test_purecomponent/ReactPureComponentExample.tsx +7 -0
- package/hooks/validators/test_git_checks.py +295 -0
- package/hooks/validators/test_good.tsx +5 -0
- package/hooks/validators/test_health_check.py +57 -0
- package/hooks/validators/test_magic_value_checks.py +63 -0
- package/hooks/validators/test_mypy_integration.py +27 -0
- package/hooks/validators/test_output_formatter.py +150 -0
- package/hooks/validators/test_pr_reference_checks.py +41 -0
- package/hooks/validators/test_python_antipattern_checks.py +113 -0
- package/hooks/validators/test_python_style_checks.py +439 -0
- package/hooks/validators/test_react_checks.py +213 -0
- package/hooks/validators/test_results.txt +25 -0
- package/hooks/validators/test_ruff_integration.py +27 -0
- package/hooks/validators/test_run_all_validators.py +228 -0
- package/hooks/validators/test_run_all_validators_integration.py +48 -0
- package/hooks/validators/test_safety_checks.py +243 -0
- package/hooks/validators/test_security_checks.py +105 -0
- package/hooks/validators/test_test_safety_checks.py +321 -0
- package/hooks/validators/test_todo_checks.py +39 -0
- package/hooks/validators/test_type_safety_checks.py +85 -0
- package/hooks/validators/test_useless_test_checks.py +55 -0
- package/hooks/validators/test_validator_base.py +26 -0
- package/hooks/validators/test_verify_paths.py +34 -0
- package/hooks/validators/todo_checks.py +59 -0
- package/hooks/validators/type_safety_checks.py +101 -0
- package/hooks/validators/useless_test_checks.py +92 -0
- package/hooks/validators/validator_base.py +19 -0
- package/hooks/validators/verify_paths.py +57 -0
- package/hooks/workflow/auto-formatter.py +114 -0
- package/hooks/workflow/investigation-tracker-reset.py +46 -0
- package/package.json +30 -0
- package/rules/agent-spawn-protocol.md +47 -0
- package/rules/cleanup-temp-files.md +27 -0
- package/rules/code-reviews.md +11 -0
- package/rules/code-standards.md +43 -0
- package/rules/conservative-action.md +20 -0
- package/rules/context7.md +12 -0
- package/rules/explore-thoroughly.md +27 -0
- package/rules/git-workflow.md +42 -0
- package/rules/parallel-tools.md +23 -0
- package/rules/research-mode.md +23 -0
- package/rules/right-sized-engineering.md +28 -0
- package/rules/tdd.md +7 -0
- package/rules/testing.md +12 -0
- package/skills/agent-prompt/SKILL.md +102 -0
- package/skills/anthropic-plan/SKILL.md +107 -0
- package/skills/everything-search/SKILL.md +144 -0
- package/skills/ingest/SKILL.md +40 -0
- package/skills/npm-creator/SKILL.md +183 -0
- package/skills/pr-review-responder/EXAMPLES.md +590 -0
- package/skills/pr-review-responder/PRINCIPLES.md +539 -0
- package/skills/pr-review-responder/README.md +209 -0
- package/skills/pr-review-responder/SKILL.md +202 -0
- package/skills/pr-review-responder/TESTING.md +407 -0
- package/skills/pr-review-responder/scripts/respond_to_reviews.py +376 -0
- package/skills/pr-review-responder/update_skill.py +297 -0
- package/skills/prompt-generator/REFERENCE.md +150 -0
- package/skills/prompt-generator/SKILL.md +154 -0
- package/skills/readability-review/SKILL.md +127 -0
- package/skills/recall/SKILL.md +27 -0
- package/skills/remember/SKILL.md +63 -0
- package/skills/rule-audit/SKILL.md +307 -0
- package/skills/rule-creator/SKILL.md +150 -0
- package/skills/skill-writer/REFERENCE.md +246 -0
- package/skills/skill-writer/SKILL.md +270 -0
- package/skills/tdd-team/SKILL.md +128 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Output formatting for validators.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- Colored terminal output
|
|
5
|
+
- Contextual diff display
|
|
6
|
+
- Progress indicators
|
|
7
|
+
- JSON output for CI
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from typing import Dict, List, TypedDict
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ViolationDict(TypedDict):
|
|
18
|
+
file: str
|
|
19
|
+
line: int
|
|
20
|
+
message: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ValidatorResultDict(TypedDict):
|
|
24
|
+
name: str
|
|
25
|
+
checks: str
|
|
26
|
+
passed: bool
|
|
27
|
+
output: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OutputMode(Enum):
|
|
31
|
+
TEXT = "text"
|
|
32
|
+
JSON = "json"
|
|
33
|
+
COMPACT = "compact"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def colorize(text: str, color: str, enabled: bool = True) -> str:
|
|
37
|
+
"""Add ANSI color codes to text.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
text: Text to colorize
|
|
41
|
+
color: Color name (red, green, yellow, blue)
|
|
42
|
+
enabled: Whether to apply colors
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Colorized text or original if disabled
|
|
46
|
+
"""
|
|
47
|
+
if not enabled:
|
|
48
|
+
return text
|
|
49
|
+
|
|
50
|
+
colors = {
|
|
51
|
+
"red": "\033[91m",
|
|
52
|
+
"green": "\033[92m",
|
|
53
|
+
"yellow": "\033[93m",
|
|
54
|
+
"blue": "\033[94m",
|
|
55
|
+
"bold": "\033[1m",
|
|
56
|
+
"reset": "\033[0m",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
color_code = colors.get(color, "")
|
|
60
|
+
reset = colors["reset"]
|
|
61
|
+
|
|
62
|
+
return f"{color_code}{text}{reset}"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def format_violation_with_context(
|
|
66
|
+
source: str,
|
|
67
|
+
line_num: int,
|
|
68
|
+
message: str,
|
|
69
|
+
context_lines: int = 2,
|
|
70
|
+
use_colors: bool = True,
|
|
71
|
+
) -> str:
|
|
72
|
+
"""Format a violation with surrounding code context.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
source: Full source code
|
|
76
|
+
line_num: Line number of violation (1-indexed)
|
|
77
|
+
message: Violation message
|
|
78
|
+
context_lines: Number of context lines before/after
|
|
79
|
+
use_colors: Whether to use ANSI colors
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Formatted violation with context
|
|
83
|
+
"""
|
|
84
|
+
lines = source.splitlines()
|
|
85
|
+
start = max(0, line_num - context_lines - 1)
|
|
86
|
+
end = min(len(lines), line_num + context_lines)
|
|
87
|
+
|
|
88
|
+
output_lines: List[str] = []
|
|
89
|
+
output_lines.append(colorize(f" {message}", "red", use_colors))
|
|
90
|
+
output_lines.append("")
|
|
91
|
+
|
|
92
|
+
for i in range(start, end):
|
|
93
|
+
line_number = i + 1
|
|
94
|
+
line_content = lines[i]
|
|
95
|
+
prefix = ">" if line_number == line_num else " "
|
|
96
|
+
|
|
97
|
+
if line_number == line_num:
|
|
98
|
+
formatted = colorize(
|
|
99
|
+
f" {prefix} {line_number:4d} | {line_content}",
|
|
100
|
+
"red",
|
|
101
|
+
use_colors,
|
|
102
|
+
)
|
|
103
|
+
else:
|
|
104
|
+
formatted = f" {line_number:4d} | {line_content}"
|
|
105
|
+
|
|
106
|
+
output_lines.append(formatted)
|
|
107
|
+
|
|
108
|
+
output_lines.append("")
|
|
109
|
+
return "\n".join(output_lines)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass(frozen=True)
|
|
113
|
+
class OutputFormatter:
|
|
114
|
+
"""Formats validator output for display."""
|
|
115
|
+
|
|
116
|
+
mode: OutputMode = OutputMode.TEXT
|
|
117
|
+
use_colors: bool = True
|
|
118
|
+
show_context: bool = True
|
|
119
|
+
context_lines: int = 2
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def effective_use_colors(self) -> bool:
|
|
123
|
+
"""Return whether colors should be used, accounting for TTY detection."""
|
|
124
|
+
return self.use_colors and sys.stdout.isatty()
|
|
125
|
+
|
|
126
|
+
def format_header(self, title: str) -> str:
|
|
127
|
+
"""Format a section header."""
|
|
128
|
+
if self.mode == OutputMode.JSON:
|
|
129
|
+
return ""
|
|
130
|
+
|
|
131
|
+
separator = "=" * 60
|
|
132
|
+
return f"\n{separator}\n{title}\n{separator}\n"
|
|
133
|
+
|
|
134
|
+
def format_progress(self, current: int, total: int, name: str) -> str:
|
|
135
|
+
"""Format a progress indicator."""
|
|
136
|
+
if self.mode == OutputMode.JSON:
|
|
137
|
+
return ""
|
|
138
|
+
|
|
139
|
+
bar_width = 20
|
|
140
|
+
filled = int(bar_width * current / total)
|
|
141
|
+
bar = "#" * filled + "-" * (bar_width - filled)
|
|
142
|
+
|
|
143
|
+
return f"[{bar}] {current}/{total} {name}"
|
|
144
|
+
|
|
145
|
+
def format_result(
|
|
146
|
+
self,
|
|
147
|
+
name: str,
|
|
148
|
+
checks: str,
|
|
149
|
+
passed: bool,
|
|
150
|
+
output: str,
|
|
151
|
+
) -> str:
|
|
152
|
+
"""Format a single validator result."""
|
|
153
|
+
if self.mode == OutputMode.JSON:
|
|
154
|
+
return ""
|
|
155
|
+
|
|
156
|
+
use_colors = self.effective_use_colors
|
|
157
|
+
status = colorize("[PASS]", "green", use_colors) if passed else colorize("[FAIL]", "red", use_colors)
|
|
158
|
+
|
|
159
|
+
lines = [f"{status} {name} (checks {checks})"]
|
|
160
|
+
|
|
161
|
+
if not passed and output:
|
|
162
|
+
for line in output.strip().split("\n"):
|
|
163
|
+
if line:
|
|
164
|
+
lines.append(f" {line}")
|
|
165
|
+
|
|
166
|
+
return "\n".join(lines)
|
|
167
|
+
|
|
168
|
+
def format_results(self, results: List[ValidatorResultDict]) -> str:
|
|
169
|
+
"""Format all validator results."""
|
|
170
|
+
if self.mode == OutputMode.JSON:
|
|
171
|
+
return json.dumps({"results": results}, indent=2)
|
|
172
|
+
|
|
173
|
+
output_lines: List[str] = []
|
|
174
|
+
|
|
175
|
+
for result in results:
|
|
176
|
+
output_lines.append(
|
|
177
|
+
self.format_result(
|
|
178
|
+
name=result["name"],
|
|
179
|
+
checks=result.get("checks", ""),
|
|
180
|
+
passed=result["passed"],
|
|
181
|
+
output=result.get("output", ""),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
return "\n".join(output_lines)
|
|
186
|
+
|
|
187
|
+
def format_summary(self, passed: int, failed: int) -> str:
|
|
188
|
+
"""Format the final summary."""
|
|
189
|
+
if self.mode == OutputMode.JSON:
|
|
190
|
+
return ""
|
|
191
|
+
|
|
192
|
+
use_colors = self.effective_use_colors
|
|
193
|
+
separator = "=" * 60
|
|
194
|
+
|
|
195
|
+
if failed == 0:
|
|
196
|
+
verdict = colorize("READY TO PUSH", "green", use_colors)
|
|
197
|
+
detail = "All automated checks passed"
|
|
198
|
+
else:
|
|
199
|
+
verdict = colorize("VIOLATIONS FOUND", "red", use_colors)
|
|
200
|
+
detail = f"{failed} check(s) failed"
|
|
201
|
+
|
|
202
|
+
return f"\n{separator}\nVERDICT: {verdict} - {detail}\n{separator}\n"
|
|
203
|
+
|
|
204
|
+
def format_stats(
|
|
205
|
+
self,
|
|
206
|
+
files_checked: int,
|
|
207
|
+
violations_found: int,
|
|
208
|
+
time_elapsed: float,
|
|
209
|
+
) -> str:
|
|
210
|
+
"""Format statistics."""
|
|
211
|
+
if self.mode == OutputMode.JSON:
|
|
212
|
+
return ""
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
f"\nStats: {files_checked} files checked, "
|
|
216
|
+
f"{violations_found} violations found, "
|
|
217
|
+
f"{time_elapsed:.2f}s elapsed\n"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def group_violations_by_file(violations: List[ViolationDict]) -> Dict[str, List[ViolationDict]]:
|
|
222
|
+
"""Group violations by file path.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
violations: List of violation dicts with 'file' key
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Dict mapping file paths to their violations
|
|
229
|
+
"""
|
|
230
|
+
grouped: Dict[str, List[ViolationDict]] = {}
|
|
231
|
+
|
|
232
|
+
for violation in violations:
|
|
233
|
+
file_path = violation.get("file", "unknown")
|
|
234
|
+
if file_path not in grouped:
|
|
235
|
+
grouped[file_path] = []
|
|
236
|
+
grouped[file_path].append(violation)
|
|
237
|
+
|
|
238
|
+
return grouped
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def format_grouped_violations(
|
|
242
|
+
grouped: Dict[str, List[ViolationDict]],
|
|
243
|
+
use_colors: bool = True,
|
|
244
|
+
) -> str:
|
|
245
|
+
"""Format violations grouped by file.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
grouped: Dict from group_violations_by_file
|
|
249
|
+
use_colors: Whether to use ANSI colors
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Formatted string with file headers
|
|
253
|
+
"""
|
|
254
|
+
output_lines: List[str] = []
|
|
255
|
+
|
|
256
|
+
for file_path, violations in sorted(grouped.items()):
|
|
257
|
+
output_lines.append("")
|
|
258
|
+
output_lines.append(colorize(f" {file_path}", "bold", use_colors))
|
|
259
|
+
output_lines.append(" " + "-" * (len(file_path) + 2))
|
|
260
|
+
|
|
261
|
+
for violation in sorted(violations, key=lambda v: v.get("line", 0)):
|
|
262
|
+
line = violation.get("line", "?")
|
|
263
|
+
message = violation.get("message", "Unknown violation")
|
|
264
|
+
output_lines.append(f" Line {line}: {message}")
|
|
265
|
+
|
|
266
|
+
return "\n".join(output_lines)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""PR and commit reference detection validator.
|
|
2
|
+
|
|
3
|
+
Implements check 6: No PR/commit references in comments.
|
|
4
|
+
Code comments must be timeless - no historical context.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List
|
|
11
|
+
|
|
12
|
+
from validator_base import Violation
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
PR_PATTERN = re.compile(r"#.*\bPR\s*#?\d+", re.IGNORECASE)
|
|
16
|
+
COMMIT_PATTERN = re.compile(r"#.*\bcommit\b", re.IGNORECASE)
|
|
17
|
+
ISSUE_PATTERN = re.compile(r"#.*(?:addresses|fixes|closes|resolves)\s*#\d+", re.IGNORECASE)
|
|
18
|
+
HASH_REF_PATTERN = re.compile(r"#\s*#\d+")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_pr_references(source: str, filename: str) -> List[Violation]:
|
|
22
|
+
violations: List[Violation] = []
|
|
23
|
+
lines = source.splitlines()
|
|
24
|
+
|
|
25
|
+
for line_num, line in enumerate(lines, start=1):
|
|
26
|
+
if PR_PATTERN.search(line):
|
|
27
|
+
violations.append(
|
|
28
|
+
Violation(filename, line_num, "PR reference in comment - comments should be timeless")
|
|
29
|
+
)
|
|
30
|
+
elif COMMIT_PATTERN.search(line):
|
|
31
|
+
violations.append(
|
|
32
|
+
Violation(filename, line_num, "Commit reference in comment - comments should be timeless")
|
|
33
|
+
)
|
|
34
|
+
elif ISSUE_PATTERN.search(line) or HASH_REF_PATTERN.search(line):
|
|
35
|
+
violations.append(
|
|
36
|
+
Violation(filename, line_num, "Issue reference in comment - comments should be timeless")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return violations
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_file(file_path: Path) -> List[Violation]:
|
|
43
|
+
filename = str(file_path)
|
|
44
|
+
try:
|
|
45
|
+
source = file_path.read_text(encoding="utf-8")
|
|
46
|
+
except Exception as error:
|
|
47
|
+
return [Violation(filename, 0, f"Error reading file: {error}")]
|
|
48
|
+
|
|
49
|
+
return check_pr_references(source, filename)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main() -> int:
|
|
53
|
+
if len(sys.argv) < 2:
|
|
54
|
+
print("Usage: pr_reference_checks.py <file1> [file2 ...]", file=sys.stderr)
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
all_violations: List[Violation] = []
|
|
58
|
+
for file_arg in sys.argv[1:]:
|
|
59
|
+
file_path = Path(file_arg)
|
|
60
|
+
if not file_path.exists():
|
|
61
|
+
print(f"Error: File not found: {file_path}", file=sys.stderr)
|
|
62
|
+
return 1
|
|
63
|
+
all_violations.extend(validate_file(file_path))
|
|
64
|
+
|
|
65
|
+
for violation in all_violations:
|
|
66
|
+
print(violation)
|
|
67
|
+
|
|
68
|
+
return 1 if all_violations else 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
sys.exit(main())
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""Python anti-pattern detection validator.
|
|
2
|
+
|
|
3
|
+
Implements:
|
|
4
|
+
- Check 33: Mutable default arguments
|
|
5
|
+
- Check 34: Bare except clauses
|
|
6
|
+
- Check 35: Print in production code
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import ast
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List
|
|
13
|
+
|
|
14
|
+
from validator_base import Violation
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check_mutable_default_args(tree: ast.AST, filename: str) -> List[Violation]:
|
|
18
|
+
violations: List[Violation] = []
|
|
19
|
+
|
|
20
|
+
for node in ast.walk(tree):
|
|
21
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
22
|
+
for default in node.args.defaults:
|
|
23
|
+
if isinstance(default, (ast.List, ast.Dict, ast.Set)):
|
|
24
|
+
violations.append(
|
|
25
|
+
Violation(
|
|
26
|
+
filename,
|
|
27
|
+
node.lineno,
|
|
28
|
+
f"Mutable default argument in '{node.name}' - use None and initialize inside",
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return violations
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def check_bare_except(tree: ast.AST, filename: str) -> List[Violation]:
|
|
36
|
+
violations: List[Violation] = []
|
|
37
|
+
|
|
38
|
+
for node in ast.walk(tree):
|
|
39
|
+
if isinstance(node, ast.ExceptHandler):
|
|
40
|
+
if node.type is None:
|
|
41
|
+
violations.append(
|
|
42
|
+
Violation(
|
|
43
|
+
filename,
|
|
44
|
+
node.lineno,
|
|
45
|
+
"Bare except clause - specify exception type",
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return violations
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def check_print_in_production(tree: ast.AST, filename: str) -> List[Violation]:
|
|
53
|
+
violations: List[Violation] = []
|
|
54
|
+
|
|
55
|
+
if "test" in filename.lower():
|
|
56
|
+
return violations
|
|
57
|
+
|
|
58
|
+
for node in ast.walk(tree):
|
|
59
|
+
if isinstance(node, ast.Call):
|
|
60
|
+
if isinstance(node.func, ast.Name) and node.func.id == "print":
|
|
61
|
+
violations.append(
|
|
62
|
+
Violation(
|
|
63
|
+
filename,
|
|
64
|
+
node.lineno,
|
|
65
|
+
"print() in production code - use logging instead",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return violations
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_file(file_path: Path) -> List[Violation]:
|
|
73
|
+
violations: List[Violation] = []
|
|
74
|
+
filename = str(file_path)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
source = file_path.read_text(encoding="utf-8")
|
|
78
|
+
tree = ast.parse(source)
|
|
79
|
+
except Exception as error:
|
|
80
|
+
return [Violation(filename, 0, f"Error: {error}")]
|
|
81
|
+
|
|
82
|
+
violations.extend(check_mutable_default_args(tree, filename))
|
|
83
|
+
violations.extend(check_bare_except(tree, filename))
|
|
84
|
+
violations.extend(check_print_in_production(tree, filename))
|
|
85
|
+
|
|
86
|
+
return violations
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def main() -> int:
|
|
90
|
+
if len(sys.argv) < 2:
|
|
91
|
+
print("Usage: python_antipattern_checks.py <file1.py> [file2.py ...]", file=sys.stderr)
|
|
92
|
+
return 1
|
|
93
|
+
|
|
94
|
+
all_violations: List[Violation] = []
|
|
95
|
+
|
|
96
|
+
for file_arg in sys.argv[1:]:
|
|
97
|
+
file_path = Path(file_arg)
|
|
98
|
+
if not file_path.exists():
|
|
99
|
+
print(f"Error: File not found: {file_path}", file=sys.stderr)
|
|
100
|
+
return 1
|
|
101
|
+
all_violations.extend(validate_file(file_path))
|
|
102
|
+
|
|
103
|
+
for violation in all_violations:
|
|
104
|
+
print(violation)
|
|
105
|
+
|
|
106
|
+
return 1 if all_violations else 0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
sys.exit(main())
|