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,57 @@
1
+ """Tests for validator health checks."""
2
+
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from health_check import (
9
+ ValidatorHealth,
10
+ check_validator_exists,
11
+ check_all_validators,
12
+ get_validator_version,
13
+ )
14
+
15
+
16
+ class TestValidatorExists:
17
+ def test_existing_validator_healthy(self) -> None:
18
+ with tempfile.NamedTemporaryFile(suffix=".py", delete=False) as temp_file:
19
+ temp_file.write(b"print('hello')")
20
+ temp_path = Path(temp_file.name)
21
+
22
+ try:
23
+ result = check_validator_exists(temp_path)
24
+ assert result.healthy is True
25
+ assert result.error is None
26
+ finally:
27
+ temp_path.unlink()
28
+
29
+ def test_missing_validator_unhealthy(self) -> None:
30
+ result = check_validator_exists(Path("/nonexistent/validator.py"))
31
+ assert result.healthy is False
32
+ assert "not found" in result.error.lower()
33
+
34
+
35
+ class TestCheckAllValidators:
36
+ def test_returns_all_validator_statuses(self) -> None:
37
+ validators_dir = Path(__file__).parent
38
+ results = check_all_validators(validators_dir)
39
+ assert isinstance(results, dict)
40
+ assert "python_style_checks" in results
41
+
42
+
43
+ class TestGetValidatorVersion:
44
+ def test_version_changes_when_content_changes(self) -> None:
45
+ with tempfile.TemporaryDirectory() as temp_dir:
46
+ temp_path = Path(temp_dir)
47
+ validator_file = temp_path / "python_style_checks.py"
48
+
49
+ validator_file.write_text("# version 1")
50
+ version1 = get_validator_version(temp_path)
51
+
52
+ validator_file.write_text("# version 2 - different content")
53
+ version2 = get_validator_version(temp_path)
54
+
55
+ assert version1 != version2
56
+ assert isinstance(version1, str)
57
+ assert len(version1) > 0
@@ -0,0 +1,63 @@
1
+ """Tests for magic value detection."""
2
+
3
+ import ast
4
+
5
+ import pytest
6
+
7
+ from magic_value_checks import (
8
+ check_magic_values,
9
+ )
10
+ from validator_base import Violation
11
+
12
+
13
+ GOOD_NAMED_CONSTANTS = '''
14
+ API_TIMEOUT_MS = 5000
15
+ HASH_DELIMITER = "__"
16
+
17
+ def process():
18
+ timeout = API_TIMEOUT_MS
19
+ return f"key{HASH_DELIMITER}value"
20
+ '''
21
+
22
+ BAD_MAGIC_NUMBER = '''
23
+ def process():
24
+ timeout = 5000
25
+ return timeout
26
+ '''
27
+
28
+ ALLOWED_SMALL_NUMBERS = '''
29
+ def process():
30
+ count = 0
31
+ increment = 1
32
+ doubled = count * 2
33
+ return count + 1
34
+ '''
35
+
36
+ ALLOWED_EMPTY_STRING = '''
37
+ def process():
38
+ result = ""
39
+ return result
40
+ '''
41
+
42
+
43
+ class TestMagicValues:
44
+ def test_named_constants_pass(self) -> None:
45
+ tree = ast.parse(GOOD_NAMED_CONSTANTS)
46
+ violations = check_magic_values(tree, "test.py")
47
+ assert violations == []
48
+
49
+ def test_magic_number_fails(self) -> None:
50
+ tree = ast.parse(BAD_MAGIC_NUMBER)
51
+ violations = check_magic_values(tree, "test.py")
52
+ assert len(violations) == 1
53
+ assert "5000" in violations[0].message
54
+
55
+ def test_small_numbers_allowed(self) -> None:
56
+ tree = ast.parse(ALLOWED_SMALL_NUMBERS)
57
+ violations = check_magic_values(tree, "test.py")
58
+ assert violations == []
59
+
60
+ def test_empty_string_allowed(self) -> None:
61
+ tree = ast.parse(ALLOWED_EMPTY_STRING)
62
+ violations = check_magic_values(tree, "test.py")
63
+ assert violations == []
@@ -0,0 +1,27 @@
1
+ """Tests for mypy integration module."""
2
+
3
+ from pathlib import Path
4
+ from unittest.mock import patch
5
+
6
+ from mypy_integration import MypyResult, check_mypy_available, run_mypy_check
7
+
8
+
9
+ def test_mypy_result_dataclass() -> None:
10
+ """Test MypyResult dataclass creation."""
11
+ result = MypyResult(passed=True, output="test", error_count=0)
12
+ assert result.passed is True
13
+ assert result.output == "test"
14
+ assert result.error_count == 0
15
+
16
+
17
+ def test_check_mypy_available_returns_false_when_not_installed() -> None:
18
+ """Test that check_mypy_available returns False when mypy not found."""
19
+ with patch("subprocess.run", side_effect=FileNotFoundError):
20
+ assert check_mypy_available() is False
21
+
22
+
23
+ def test_run_mypy_check_returns_passed_for_empty_files() -> None:
24
+ """Test that run_mypy_check passes with no files."""
25
+ result = run_mypy_check([])
26
+ assert result.passed is True
27
+ assert "No files" in result.output
@@ -0,0 +1,150 @@
1
+ """Tests for output formatting."""
2
+
3
+ import pytest
4
+
5
+ from output_formatter import (
6
+ OutputFormatter,
7
+ OutputMode,
8
+ format_violation_with_context,
9
+ colorize,
10
+ group_violations_by_file,
11
+ format_grouped_violations,
12
+ ViolationDict,
13
+ ValidatorResultDict,
14
+ )
15
+
16
+
17
+ class TestColorize:
18
+ def test_colorize_pass(self) -> None:
19
+ result = colorize("PASS", "green")
20
+ assert "PASS" in result
21
+
22
+ def test_colorize_fail(self) -> None:
23
+ result = colorize("FAIL", "red")
24
+ assert "FAIL" in result
25
+
26
+ def test_colorize_disabled(self) -> None:
27
+ result = colorize("PASS", "green", enabled=False)
28
+ assert result == "PASS"
29
+
30
+
31
+ class TestFormatViolationWithContext:
32
+ def test_shows_surrounding_lines(self) -> None:
33
+ source = '''line 1
34
+ line 2
35
+ line 3 with error
36
+ line 4
37
+ line 5'''
38
+ result = format_violation_with_context(
39
+ source=source,
40
+ line_num=3,
41
+ message="Error on line 3",
42
+ context_lines=1,
43
+ )
44
+ assert "line 2" in result
45
+ assert "line 3 with error" in result
46
+ assert "line 4" in result
47
+
48
+ def test_highlights_error_line(self) -> None:
49
+ source = '''line 1
50
+ line 2
51
+ line 3 with error
52
+ line 4'''
53
+ result = format_violation_with_context(
54
+ source=source,
55
+ line_num=3,
56
+ message="Error",
57
+ context_lines=1,
58
+ )
59
+ assert ">" in result or "3" in result
60
+
61
+
62
+ class TestOutputFormatter:
63
+ def test_json_mode_output(self) -> None:
64
+ formatter = OutputFormatter(mode=OutputMode.JSON)
65
+ results: list[ValidatorResultDict] = [
66
+ {"name": "Test", "checks": "1", "passed": True, "output": "OK"}
67
+ ]
68
+ output = formatter.format_results(results)
69
+ assert '"name"' in output
70
+ assert '"passed"' in output
71
+
72
+ def test_text_mode_output(self) -> None:
73
+ formatter = OutputFormatter(mode=OutputMode.TEXT)
74
+ results: list[ValidatorResultDict] = [
75
+ {"name": "Test", "checks": "1", "passed": True, "output": "OK"}
76
+ ]
77
+ output = formatter.format_results(results)
78
+ assert "Test" in output
79
+ assert "PASS" in output or "OK" in output
80
+
81
+ def test_effective_use_colors_false_when_disabled(self) -> None:
82
+ formatter = OutputFormatter(mode=OutputMode.TEXT, use_colors=False)
83
+ assert formatter.effective_use_colors is False
84
+
85
+ def test_formatter_is_immutable(self) -> None:
86
+ formatter = OutputFormatter(mode=OutputMode.TEXT)
87
+ with pytest.raises(AttributeError):
88
+ formatter.mode = OutputMode.JSON
89
+
90
+
91
+ class TestJsonFlag:
92
+ def test_json_flag_produces_valid_json(self) -> None:
93
+ import json
94
+ import subprocess
95
+
96
+ result = subprocess.run(
97
+ ["python", "run_all_validators.py", "--json"],
98
+ capture_output=True,
99
+ text=True,
100
+ cwd=os.path.dirname(os.path.abspath(__file__)),
101
+ )
102
+
103
+ output = result.stdout.strip()
104
+ parsed = json.loads(output)
105
+ assert "results" in parsed
106
+ assert isinstance(parsed["results"], list)
107
+
108
+
109
+ class TestGroupViolationsByFile:
110
+ def test_groups_violations_by_file_path(self) -> None:
111
+ violations: list[ViolationDict] = [
112
+ {"file": "a.py", "line": 1, "message": "error 1"},
113
+ {"file": "b.py", "line": 2, "message": "error 2"},
114
+ {"file": "a.py", "line": 3, "message": "error 3"},
115
+ ]
116
+ grouped = group_violations_by_file(violations)
117
+
118
+ assert len(grouped) == 2
119
+ assert len(grouped["a.py"]) == 2
120
+ assert len(grouped["b.py"]) == 1
121
+
122
+ def test_empty_list_returns_empty_dict(self) -> None:
123
+ grouped = group_violations_by_file([])
124
+ assert grouped == {}
125
+
126
+
127
+ class TestFormatGroupedViolations:
128
+ def test_formats_with_file_headers(self) -> None:
129
+ violations: list[ViolationDict] = [
130
+ {"file": "a.py", "line": 1, "message": "error 1"},
131
+ {"file": "a.py", "line": 3, "message": "error 2"},
132
+ ]
133
+ grouped = group_violations_by_file(violations)
134
+ output = format_grouped_violations(grouped, use_colors=False)
135
+
136
+ assert "a.py" in output
137
+ assert "Line 1:" in output
138
+ assert "Line 3:" in output
139
+
140
+ def test_sorts_violations_by_line_number(self) -> None:
141
+ violations: list[ViolationDict] = [
142
+ {"file": "a.py", "line": 10, "message": "later"},
143
+ {"file": "a.py", "line": 2, "message": "earlier"},
144
+ ]
145
+ grouped = group_violations_by_file(violations)
146
+ output = format_grouped_violations(grouped, use_colors=False)
147
+
148
+ earlier_pos = output.find("Line 2:")
149
+ later_pos = output.find("Line 10:")
150
+ assert earlier_pos < later_pos
@@ -0,0 +1,41 @@
1
+ """Tests for PR/commit reference detection in comments."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from pr_reference_checks import (
8
+ check_pr_references,
9
+ validate_file,
10
+ )
11
+ from validator_base import Violation
12
+
13
+
14
+ GOOD_NO_REFERENCES = '''
15
+ def process():
16
+ result = calculate()
17
+ return result
18
+ '''
19
+
20
+
21
+ class TestPrReferences:
22
+ def test_no_references_pass(self) -> None:
23
+ violations = check_pr_references(GOOD_NO_REFERENCES, "test.py")
24
+ assert violations == []
25
+
26
+ def test_pr_reference_in_comment_fails(self) -> None:
27
+ code = "# Fixed in PR #99\ndef foo(): pass"
28
+ violations = check_pr_references(code, "test.py")
29
+ assert len(violations) == 1
30
+ assert "PR" in violations[0].message
31
+
32
+ def test_commit_reference_fails(self) -> None:
33
+ code = "# See commit abc123\ndef foo(): pass"
34
+ violations = check_pr_references(code, "test.py")
35
+ assert len(violations) == 1
36
+ assert "commit" in violations[0].message.lower()
37
+
38
+ def test_issue_hash_reference_fails(self) -> None:
39
+ code = "# Addresses #17\ndef foo(): pass"
40
+ violations = check_pr_references(code, "test.py")
41
+ assert len(violations) == 1
@@ -0,0 +1,113 @@
1
+ """Tests for Python anti-pattern detection."""
2
+
3
+ import ast
4
+
5
+ import pytest
6
+
7
+ from python_antipattern_checks import (
8
+ check_mutable_default_args,
9
+ check_bare_except,
10
+ check_print_in_production,
11
+ )
12
+ from validator_base import Violation
13
+
14
+
15
+ GOOD_NONE_DEFAULT = '''
16
+ def process(items=None):
17
+ if items is None:
18
+ items = []
19
+ return items
20
+ '''
21
+
22
+ BAD_MUTABLE_DEFAULT = '''
23
+ def process(items=[]):
24
+ return items
25
+ '''
26
+
27
+ BAD_DICT_DEFAULT = '''
28
+ def process(config={}):
29
+ return config
30
+ '''
31
+
32
+ GOOD_SPECIFIC_EXCEPT = '''
33
+ def process():
34
+ try:
35
+ do_work()
36
+ except ValueError:
37
+ handle_error()
38
+ '''
39
+
40
+ BAD_BARE_EXCEPT = '''
41
+ def process():
42
+ try:
43
+ do_work()
44
+ except:
45
+ handle_error()
46
+ '''
47
+
48
+ GOOD_LOGGING = '''
49
+ import logging
50
+
51
+ def process():
52
+ logging.info("Processing")
53
+ '''
54
+
55
+ BAD_PRINT = '''
56
+ def process():
57
+ print("Debug info")
58
+ '''
59
+
60
+ TEST_FILE_WITH_PRINT = '''
61
+ def test_something():
62
+ print("Test output")
63
+ assert True
64
+ '''
65
+
66
+
67
+ class TestMutableDefaultArgs:
68
+ def test_none_default_passes(self) -> None:
69
+ tree = ast.parse(GOOD_NONE_DEFAULT)
70
+ violations = check_mutable_default_args(tree, "test.py")
71
+ assert violations == []
72
+
73
+ def test_list_default_fails(self) -> None:
74
+ tree = ast.parse(BAD_MUTABLE_DEFAULT)
75
+ violations = check_mutable_default_args(tree, "test.py")
76
+ assert len(violations) == 1
77
+ assert "mutable" in violations[0].message.lower()
78
+
79
+ def test_dict_default_fails(self) -> None:
80
+ tree = ast.parse(BAD_DICT_DEFAULT)
81
+ violations = check_mutable_default_args(tree, "test.py")
82
+ assert len(violations) == 1
83
+
84
+
85
+ class TestBareExcept:
86
+ def test_specific_except_passes(self) -> None:
87
+ tree = ast.parse(GOOD_SPECIFIC_EXCEPT)
88
+ violations = check_bare_except(tree, "test.py")
89
+ assert violations == []
90
+
91
+ def test_bare_except_fails(self) -> None:
92
+ tree = ast.parse(BAD_BARE_EXCEPT)
93
+ violations = check_bare_except(tree, "test.py")
94
+ assert len(violations) == 1
95
+ assert "bare" in violations[0].message.lower() or "except" in violations[0].message.lower()
96
+
97
+
98
+ class TestPrintInProduction:
99
+ def test_logging_passes(self) -> None:
100
+ tree = ast.parse(GOOD_LOGGING)
101
+ violations = check_print_in_production(tree, "utils.py")
102
+ assert violations == []
103
+
104
+ def test_print_fails(self) -> None:
105
+ tree = ast.parse(BAD_PRINT)
106
+ violations = check_print_in_production(tree, "utils.py")
107
+ assert len(violations) == 1
108
+ assert "print" in violations[0].message.lower()
109
+
110
+ def test_print_in_test_file_allowed(self) -> None:
111
+ tree = ast.parse(TEST_FILE_WITH_PRINT)
112
+ violations = check_print_in_production(tree, "test_utils.py")
113
+ assert violations == []