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,321 @@
1
+ #!/usr/bin/env python3
2
+ """Tests for AST-based test safety validators."""
3
+
4
+ import tempfile
5
+ from pathlib import Path
6
+ from typing import List
7
+
8
+ import pytest
9
+
10
+ from test_safety_checks import (
11
+ Violation,
12
+ check_no_skip_decorators,
13
+ check_debug_guard_in_dev_scripts,
14
+ main,
15
+ )
16
+
17
+
18
+ class TestNoSkipDecorators:
19
+ """Test detection of skip decorators in test files."""
20
+
21
+ def test_detects_pytest_skip_decorator(self) -> None:
22
+ code = """
23
+ import pytest
24
+
25
+ @pytest.mark.skip
26
+ def test_something():
27
+ pass
28
+ """
29
+ violations = check_no_skip_decorators(code, "test_file.py")
30
+ assert len(violations) == 1
31
+ assert violations[0].line == 5
32
+ assert "skip decorator" in violations[0].message.lower()
33
+
34
+ def test_detects_pytest_skip_with_reason(self) -> None:
35
+ code = """
36
+ import pytest
37
+
38
+ @pytest.mark.skip(reason="not ready")
39
+ def test_something():
40
+ pass
41
+ """
42
+ violations = check_no_skip_decorators(code, "test_file.py")
43
+ assert len(violations) == 1
44
+ assert violations[0].line == 5
45
+
46
+ def test_detects_unittest_skip(self) -> None:
47
+ code = """
48
+ import unittest
49
+
50
+ class TestCase(unittest.TestCase):
51
+ @unittest.skip("reason")
52
+ def test_something(self):
53
+ pass
54
+ """
55
+ violations = check_no_skip_decorators(code, "test_file.py")
56
+ assert len(violations) == 1
57
+ assert violations[0].line == 6
58
+
59
+ def test_detects_skipif_decorator(self) -> None:
60
+ code = """
61
+ import pytest
62
+
63
+ @pytest.mark.skipif(True, reason="reason")
64
+ def test_something():
65
+ pass
66
+ """
67
+ violations = check_no_skip_decorators(code, "test_file.py")
68
+ assert len(violations) == 1
69
+ assert violations[0].line == 5
70
+
71
+ def test_detects_skipunless_decorator(self) -> None:
72
+ code = """
73
+ import unittest
74
+
75
+ class TestCase(unittest.TestCase):
76
+ @unittest.skipUnless(False, "reason")
77
+ def test_something(self):
78
+ pass
79
+ """
80
+ violations = check_no_skip_decorators(code, "test_file.py")
81
+ assert len(violations) == 1
82
+ assert violations[0].line == 6
83
+
84
+ def test_detects_multiple_skip_decorators(self) -> None:
85
+ code = """
86
+ import pytest
87
+
88
+ @pytest.mark.skip
89
+ def test_one():
90
+ pass
91
+
92
+ @pytest.mark.skipif(True, reason="reason")
93
+ def test_two():
94
+ pass
95
+ """
96
+ violations = check_no_skip_decorators(code, "test_file.py")
97
+ assert len(violations) == 2
98
+ assert violations[0].line == 5
99
+ assert violations[1].line == 9
100
+
101
+ def test_allows_other_decorators(self) -> None:
102
+ code = """
103
+ import pytest
104
+
105
+ @pytest.mark.parametrize("x", [1, 2, 3])
106
+ def test_something(x):
107
+ pass
108
+
109
+ @pytest.fixture
110
+ def my_fixture():
111
+ pass
112
+ """
113
+ violations = check_no_skip_decorators(code, "test_file.py")
114
+ assert len(violations) == 0
115
+
116
+ def test_allows_tests_without_decorators(self) -> None:
117
+ code = """
118
+ def test_something():
119
+ pass
120
+ """
121
+ violations = check_no_skip_decorators(code, "test_file.py")
122
+ assert len(violations) == 0
123
+
124
+ def test_detects_case_insensitive_skip_decorator(self) -> None:
125
+ """Test that decorator names are matched case-insensitively."""
126
+ code = """
127
+ import pytest
128
+
129
+ @pytest.mark.Skip
130
+ def test_something():
131
+ pass
132
+ """
133
+ violations = check_no_skip_decorators(code, "test_file.py")
134
+ assert len(violations) == 1
135
+ assert violations[0].line == 5
136
+ assert "skip decorator" in violations[0].message.lower()
137
+
138
+ def test_detects_case_insensitive_skipif(self) -> None:
139
+ """Test that SkipIf is caught."""
140
+ code = """
141
+ import unittest
142
+
143
+ class TestCase(unittest.TestCase):
144
+ @unittest.SkipIf(True, "reason")
145
+ def test_something(self):
146
+ pass
147
+ """
148
+ violations = check_no_skip_decorators(code, "test_file.py")
149
+ assert len(violations) == 1
150
+ assert violations[0].line == 6
151
+
152
+
153
+ class TestDebugGuardInDevScripts:
154
+ """Test detection of missing DEBUG checks in management commands."""
155
+
156
+ def test_detects_missing_debug_check(self) -> None:
157
+ code = """
158
+ from django.core.management.base import BaseCommand
159
+
160
+ class Command(BaseCommand):
161
+ def handle(self, *args, **options):
162
+ print("Doing dangerous thing")
163
+ """
164
+ violations = check_debug_guard_in_dev_scripts(
165
+ code, "management/commands/dev_tool.py"
166
+ )
167
+ assert len(violations) == 1
168
+ assert "DEBUG" in violations[0].message
169
+
170
+ def test_allows_debug_check_at_start(self) -> None:
171
+ code = """
172
+ from django.core.management.base import BaseCommand
173
+ from django.conf import settings
174
+
175
+ class Command(BaseCommand):
176
+ def handle(self, *args, **options):
177
+ if not settings.DEBUG:
178
+ raise CommandError("Only for development")
179
+ print("Doing thing")
180
+ """
181
+ violations = check_debug_guard_in_dev_scripts(
182
+ code, "management/commands/dev_tool.py"
183
+ )
184
+ assert len(violations) == 0
185
+
186
+ def test_allows_debug_check_with_return(self) -> None:
187
+ code = """
188
+ from django.core.management.base import BaseCommand
189
+ from django.conf import settings
190
+
191
+ class Command(BaseCommand):
192
+ def handle(self, *args, **options):
193
+ if not settings.DEBUG:
194
+ return
195
+ print("Doing thing")
196
+ """
197
+ violations = check_debug_guard_in_dev_scripts(
198
+ code, "management/commands/dev_tool.py"
199
+ )
200
+ assert len(violations) == 0
201
+
202
+ def test_ignores_non_management_commands(self) -> None:
203
+ code = """
204
+ def some_function():
205
+ print("Doing thing")
206
+ """
207
+ violations = check_debug_guard_in_dev_scripts(code, "utils/helper.py")
208
+ assert len(violations) == 0
209
+
210
+ def test_ignores_files_outside_management_commands(self) -> None:
211
+ code = """
212
+ from django.core.management.base import BaseCommand
213
+
214
+ class Command(BaseCommand):
215
+ def handle(self, *args, **options):
216
+ print("Doing thing")
217
+ """
218
+ violations = check_debug_guard_in_dev_scripts(code, "some/other/path.py")
219
+ assert len(violations) == 0
220
+
221
+ def test_allows_positive_debug_check_with_else_raise(self) -> None:
222
+ """Test that 'if settings.DEBUG: ... else: raise' is a valid guard."""
223
+ code = """
224
+ from django.core.management.base import BaseCommand
225
+ from django.conf import settings
226
+
227
+ class Command(BaseCommand):
228
+ def handle(self, *args, **options):
229
+ if settings.DEBUG:
230
+ print("OK")
231
+ else:
232
+ raise CommandError("Development only")
233
+ print("Doing thing")
234
+ """
235
+ violations = check_debug_guard_in_dev_scripts(
236
+ code, "management/commands/dev_tool.py"
237
+ )
238
+ assert len(violations) == 0
239
+
240
+ def test_allows_positive_debug_check_with_else_return(self) -> None:
241
+ """Test that 'if settings.DEBUG: ... else: return' is a valid guard."""
242
+ code = """
243
+ from django.core.management.base import BaseCommand
244
+ from django.conf import settings
245
+
246
+ class Command(BaseCommand):
247
+ def handle(self, *args, **options):
248
+ if settings.DEBUG:
249
+ pass
250
+ else:
251
+ return
252
+ print("Doing thing")
253
+ """
254
+ violations = check_debug_guard_in_dev_scripts(
255
+ code, "management/commands/dev_tool.py"
256
+ )
257
+ assert len(violations) == 0
258
+
259
+
260
+ class TestViolation:
261
+ """Test Violation dataclass."""
262
+
263
+ def test_violation_creation(self) -> None:
264
+ v = Violation("test.py", 42, "Test message")
265
+ assert v.file == "test.py"
266
+ assert v.line == 42
267
+ assert v.message == "Test message"
268
+
269
+ def test_violation_str(self) -> None:
270
+ v = Violation("test.py", 42, "Test message")
271
+ assert str(v) == "test.py:42: Test message"
272
+
273
+
274
+ class TestMainFunction:
275
+ """Test main CLI function."""
276
+
277
+ def test_main_with_no_violations(self, tmp_path: Path) -> None:
278
+ test_file = tmp_path / "test_good.py"
279
+ test_file.write_text("def test_something():\n pass\n")
280
+
281
+ exit_code = main([str(test_file)])
282
+ assert exit_code == 0
283
+
284
+ def test_main_with_violations(self, tmp_path: Path, capsys) -> None:
285
+ test_file = tmp_path / "test_bad.py"
286
+ test_file.write_text("import pytest\n\n@pytest.mark.skip\ndef test_x():\n pass\n")
287
+
288
+ exit_code = main([str(test_file)])
289
+ assert exit_code == 1
290
+
291
+ captured = capsys.readouterr()
292
+ assert ":4:" in captured.out
293
+ assert "skip decorator" in captured.out.lower()
294
+
295
+ def test_main_with_multiple_files(self, tmp_path: Path) -> None:
296
+ file1 = tmp_path / "test_good.py"
297
+ file1.write_text("def test_something():\n pass\n")
298
+
299
+ file2 = tmp_path / "test_bad.py"
300
+ file2.write_text("import pytest\n\n@pytest.mark.skip\ndef test_x():\n pass\n")
301
+
302
+ exit_code = main([str(file1), str(file2)])
303
+ assert exit_code == 1
304
+
305
+ def test_main_with_management_command(self, tmp_path: Path, capsys) -> None:
306
+ mgmt_dir = tmp_path / "management" / "commands"
307
+ mgmt_dir.mkdir(parents=True)
308
+ cmd_file = mgmt_dir / "dev_tool.py"
309
+ cmd_file.write_text("""
310
+ from django.core.management.base import BaseCommand
311
+
312
+ class Command(BaseCommand):
313
+ def handle(self, *args, **options):
314
+ print("Dangerous")
315
+ """)
316
+
317
+ exit_code = main([str(cmd_file)])
318
+ assert exit_code == 1
319
+
320
+ captured = capsys.readouterr()
321
+ assert "DEBUG" in captured.out
@@ -0,0 +1,39 @@
1
+ """Tests for TODO/FIXME detection."""
2
+
3
+ import pytest
4
+
5
+ from todo_checks import check_untracked_todos
6
+ from validator_base import Violation
7
+
8
+
9
+ GOOD_TODO_WITH_ISSUE = '''
10
+ def process():
11
+ pass
12
+ '''
13
+
14
+ BAD_TODO_WITHOUT_ISSUE = '''
15
+ def process():
16
+ pass
17
+ '''
18
+
19
+
20
+ class TestUntrackedTodos:
21
+ def test_no_todo_passes(self) -> None:
22
+ violations = check_untracked_todos(GOOD_TODO_WITH_ISSUE, "test.py")
23
+ assert violations == []
24
+
25
+ def test_todo_without_issue_fails(self) -> None:
26
+ code = "# TODO: fix this later\ndef foo(): pass"
27
+ violations = check_untracked_todos(code, "test.py")
28
+ assert len(violations) == 1
29
+ assert "TODO" in violations[0].message
30
+
31
+ def test_todo_with_issue_passes(self) -> None:
32
+ code = "# TODO #123: fix this later\ndef foo(): pass"
33
+ violations = check_untracked_todos(code, "test.py")
34
+ assert violations == []
35
+
36
+ def test_fixme_without_issue_fails(self) -> None:
37
+ code = "# FIXME: broken\ndef foo(): pass"
38
+ violations = check_untracked_todos(code, "test.py")
39
+ assert len(violations) == 1
@@ -0,0 +1,85 @@
1
+ """Tests for type safety checks."""
2
+
3
+ import ast
4
+
5
+ import pytest
6
+
7
+ from type_safety_checks import (
8
+ check_missing_type_hints,
9
+ check_any_type,
10
+ )
11
+ from validator_base import Violation
12
+
13
+
14
+ GOOD_FULLY_TYPED = '''
15
+ def process(items: list[str]) -> int:
16
+ return len(items)
17
+ '''
18
+
19
+ BAD_NO_RETURN_TYPE = '''
20
+ def process(items: list[str]):
21
+ return len(items)
22
+ '''
23
+
24
+ BAD_NO_PARAM_TYPE = '''
25
+ def process(items) -> int:
26
+ return len(items)
27
+ '''
28
+
29
+ GOOD_NO_ANY = '''
30
+ from typing import List
31
+
32
+ def process(items: List[str]) -> int:
33
+ return len(items)
34
+ '''
35
+
36
+ BAD_ANY_TYPE = '''
37
+ from typing import Any
38
+
39
+ def process(items: Any) -> int:
40
+ return len(items)
41
+ '''
42
+
43
+ BAD_ANY_RETURN = '''
44
+ from typing import Any, List
45
+
46
+ def process(items: List[str]) -> Any:
47
+ return items[0]
48
+ '''
49
+
50
+
51
+ class TestMissingTypeHints:
52
+ def test_fully_typed_passes(self) -> None:
53
+ tree = ast.parse(GOOD_FULLY_TYPED)
54
+ violations = check_missing_type_hints(tree, "test.py")
55
+ assert violations == []
56
+
57
+ def test_missing_return_type_fails(self) -> None:
58
+ tree = ast.parse(BAD_NO_RETURN_TYPE)
59
+ violations = check_missing_type_hints(tree, "test.py")
60
+ assert len(violations) == 1
61
+ assert "return" in violations[0].message.lower()
62
+
63
+ def test_missing_param_type_fails(self) -> None:
64
+ tree = ast.parse(BAD_NO_PARAM_TYPE)
65
+ violations = check_missing_type_hints(tree, "test.py")
66
+ assert len(violations) == 1
67
+ assert "items" in violations[0].message or "parameter" in violations[0].message.lower()
68
+
69
+
70
+ class TestAnyType:
71
+ def test_no_any_passes(self) -> None:
72
+ tree = ast.parse(GOOD_NO_ANY)
73
+ violations = check_any_type(tree, "test.py")
74
+ assert violations == []
75
+
76
+ def test_any_param_fails(self) -> None:
77
+ tree = ast.parse(BAD_ANY_TYPE)
78
+ violations = check_any_type(tree, "test.py")
79
+ assert len(violations) == 1
80
+ assert "Any" in violations[0].message
81
+
82
+ def test_any_return_fails(self) -> None:
83
+ tree = ast.parse(BAD_ANY_RETURN)
84
+ violations = check_any_type(tree, "test.py")
85
+ assert len(violations) == 1
@@ -0,0 +1,55 @@
1
+ """Tests for useless test detection."""
2
+
3
+ import ast
4
+
5
+ import pytest
6
+
7
+ from useless_test_checks import (
8
+ check_useless_tests,
9
+ )
10
+ from validator_base import Violation
11
+
12
+
13
+ GOOD_BEHAVIOR_TEST = '''
14
+ def test_calculate_total():
15
+ result = calculate_total([10, 20, 30])
16
+ assert result == 60
17
+ '''
18
+
19
+ BAD_CALLABLE_CHECK = '''
20
+ def test_function_exists():
21
+ assert callable(process_data)
22
+ '''
23
+
24
+ BAD_EXISTENCE_CHECK = '''
25
+ def test_constant_exists():
26
+ assert hasattr(module, "CONSTANT")
27
+ '''
28
+
29
+ BAD_CONSTANT_VALUE_TEST = '''
30
+ def test_constant_value():
31
+ assert CACHE_DIR == "cache"
32
+ '''
33
+
34
+
35
+ class TestUselessTests:
36
+ def test_behavior_test_passes(self) -> None:
37
+ tree = ast.parse(GOOD_BEHAVIOR_TEST)
38
+ violations = check_useless_tests(tree, "test_example.py")
39
+ assert violations == []
40
+
41
+ def test_callable_check_fails(self) -> None:
42
+ tree = ast.parse(BAD_CALLABLE_CHECK)
43
+ violations = check_useless_tests(tree, "test_example.py")
44
+ assert len(violations) == 1
45
+ assert "callable" in violations[0].message.lower()
46
+
47
+ def test_existence_check_fails(self) -> None:
48
+ tree = ast.parse(BAD_EXISTENCE_CHECK)
49
+ violations = check_useless_tests(tree, "test_example.py")
50
+ assert len(violations) == 1
51
+
52
+ def test_constant_value_test_fails(self) -> None:
53
+ tree = ast.parse(BAD_CONSTANT_VALUE_TEST)
54
+ violations = check_useless_tests(tree, "test_example.py")
55
+ assert len(violations) == 1
@@ -0,0 +1,26 @@
1
+ """Tests for shared validator base types."""
2
+
3
+ import pytest
4
+
5
+ from validator_base import Violation
6
+
7
+
8
+ class TestViolation:
9
+ def test_violation_str_format(self) -> None:
10
+ violation = Violation(file="test.py", line=42, message="Test message")
11
+ assert str(violation) == "test.py:42: Test message"
12
+
13
+ def test_violation_is_immutable(self) -> None:
14
+ violation = Violation(file="test.py", line=42, message="Test message")
15
+ with pytest.raises(AttributeError):
16
+ violation.file = "other.py"
17
+
18
+ def test_violation_equality(self) -> None:
19
+ v1 = Violation(file="test.py", line=42, message="Test message")
20
+ v2 = Violation(file="test.py", line=42, message="Test message")
21
+ assert v1 == v2
22
+
23
+ def test_violation_hashable(self) -> None:
24
+ violation = Violation(file="test.py", line=42, message="Test message")
25
+ violation_set = {violation}
26
+ assert violation in violation_set
@@ -0,0 +1,34 @@
1
+ """Tests for validator path verification script."""
2
+
3
+ import pytest
4
+
5
+
6
+ def test_extract_validator_paths_finds_validator_references() -> None:
7
+ """Test that validator references are extracted from markdown content."""
8
+ from verify_paths import extract_validator_paths
9
+
10
+ content = """
11
+ **Validator:** `validators/import_checks.py`
12
+ Some text here.
13
+ **Validator:** `validators/style_checks.py`
14
+ """
15
+
16
+ result = extract_validator_paths(content)
17
+
18
+ assert "import_checks.py" in result
19
+ assert "style_checks.py" in result
20
+
21
+
22
+ def test_extract_validator_paths_deduplicates() -> None:
23
+ """Test that duplicate validator references are deduplicated."""
24
+ from verify_paths import extract_validator_paths
25
+
26
+ content = """
27
+ **Validator:** `validators/import_checks.py`
28
+ **Validator:** `validators/import_checks.py`
29
+ **Validator:** `validators/import_checks.py`
30
+ """
31
+
32
+ result = extract_validator_paths(content)
33
+
34
+ assert result.count("import_checks.py") == 1
@@ -0,0 +1,59 @@
1
+ """TODO/FIXME tracking validator.
2
+
3
+ Implements check 36: TODO without issue reference.
4
+ """
5
+
6
+ import re
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import List
10
+
11
+ from validator_base import Violation
12
+
13
+
14
+ TODO_PATTERN = re.compile(r"#\s*(TODO|FIXME)\b(?!.*#\d+)", re.IGNORECASE)
15
+
16
+
17
+ def check_untracked_todos(source: str, filename: str) -> List[Violation]:
18
+ violations: List[Violation] = []
19
+ lines = source.splitlines()
20
+
21
+ for line_num, line in enumerate(lines, start=1):
22
+ if TODO_PATTERN.search(line):
23
+ violations.append(
24
+ Violation(
25
+ filename,
26
+ line_num,
27
+ "TODO/FIXME without issue reference - add #<issue_number>",
28
+ )
29
+ )
30
+
31
+ return violations
32
+
33
+
34
+ def validate_file(file_path: Path) -> List[Violation]:
35
+ filename = str(file_path)
36
+ try:
37
+ source = file_path.read_text(encoding="utf-8")
38
+ except Exception as error:
39
+ return [Violation(filename, 0, f"Error reading file: {error}")]
40
+
41
+ return check_untracked_todos(source, filename)
42
+
43
+
44
+ def main() -> int:
45
+ if len(sys.argv) < 2:
46
+ return 1
47
+
48
+ all_violations: List[Violation] = []
49
+ for file_arg in sys.argv[1:]:
50
+ all_violations.extend(validate_file(Path(file_arg)))
51
+
52
+ for violation in all_violations:
53
+ print(violation)
54
+
55
+ return 1 if all_violations else 0
56
+
57
+
58
+ if __name__ == "__main__":
59
+ sys.exit(main())