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,37 @@
1
+ """Test file with inline imports inside functions - this is a violation."""
2
+ from unittest.mock import patch
3
+
4
+ from django.test import TestCase, Client
5
+
6
+
7
+ class ImportEvolutionsViewTest(TestCase):
8
+ def setUp(self) -> None:
9
+ self.luna_l1 = "Luna L1"
10
+ self.luna_l2 = "Lunara L2"
11
+ self.luna_l3 = "Lunabella L3"
12
+
13
+ @patch('example_app.views.settings')
14
+ def test_dry_run_shows_preview_without_saving(self, mock_settings: patch) -> None:
15
+ from pathlib import Path
16
+ from tempfile import TemporaryDirectory
17
+ with TemporaryDirectory() as temp_dir:
18
+ metadata_path = Path(temp_dir) / 'data' / 'metadata.json'
19
+ print(metadata_path)
20
+
21
+ @patch('example_app.views.settings')
22
+ def test_apply_mode_sets_evolves_from(self, mock_settings: patch) -> None:
23
+ from pathlib import Path
24
+ from tempfile import TemporaryDirectory
25
+ with TemporaryDirectory() as temp_dir:
26
+ metadata_path = Path(temp_dir) / 'data' / 'metadata.json'
27
+ print(metadata_path)
28
+
29
+ @patch('example_app.views.settings')
30
+ def test_skips_already_linked_evolutions(self, mock_settings: patch) -> None:
31
+ self.luna_l2 = self.luna_l1
32
+
33
+ from pathlib import Path
34
+ from tempfile import TemporaryDirectory
35
+ with TemporaryDirectory() as temp_dir:
36
+ metadata_path = Path(temp_dir) / 'data' / 'metadata.json'
37
+ print(metadata_path)
@@ -0,0 +1,10 @@
1
+ """Management command with NO DEBUG check - SHOULD BE CAUGHT"""
2
+ from django.core.management.base import BaseCommand
3
+
4
+
5
+ class Command(BaseCommand):
6
+ help = "Test command without DEBUG check"
7
+
8
+ def handle(self, *args, **options):
9
+ print("Running without DEBUG check")
10
+ return "Success"
@@ -0,0 +1,14 @@
1
+ """Management command with proper DEBUG check - should NOT be caught"""
2
+ from django.conf import settings
3
+ from django.core.management.base import BaseCommand, CommandError
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with proper DEBUG check"
8
+
9
+ def handle(self, *args, **options):
10
+ if not settings.DEBUG:
11
+ raise CommandError("This command can only be run in DEBUG mode")
12
+
13
+ print("Running in DEBUG mode")
14
+ return "Success"
@@ -0,0 +1,14 @@
1
+ """Management command with DEBUG check using return - should NOT be caught"""
2
+ from django.conf import settings
3
+ from django.core.management.base import BaseCommand
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with DEBUG check using return"
8
+
9
+ def handle(self, *args, **options):
10
+ if not settings.DEBUG:
11
+ return "ERROR: This command requires DEBUG mode"
12
+
13
+ print("Running in DEBUG mode")
14
+ return "Success"
@@ -0,0 +1,14 @@
1
+ """Management command using imported DEBUG - EDGE CASE: should be caught (wrong pattern)"""
2
+ from django.conf import settings, DEBUG
3
+ from django.core.management.base import BaseCommand, CommandError
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with imported DEBUG"
8
+
9
+ def handle(self, *args, **options):
10
+ if not DEBUG:
11
+ raise CommandError("This command can only be run in DEBUG mode")
12
+
13
+ print("Running in DEBUG mode")
14
+ return "Success"
@@ -0,0 +1,16 @@
1
+ """Management command with DEBUG check in helper function - EDGE CASE: should be caught"""
2
+ from django.conf import settings
3
+ from django.core.management.base import BaseCommand, CommandError
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with DEBUG check in helper"
8
+
9
+ def _check_debug(self):
10
+ if not settings.DEBUG:
11
+ raise CommandError("This command can only be run in DEBUG mode")
12
+
13
+ def handle(self, *args, **options):
14
+ self._check_debug()
15
+ print("Running in DEBUG mode")
16
+ return "Success"
@@ -0,0 +1,22 @@
1
+ """Management command with DEBUG check after 5 statements - EDGE CASE: might be caught or not"""
2
+ from django.conf import settings
3
+ from django.core.management.base import BaseCommand, CommandError
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with late DEBUG check"
8
+
9
+ def handle(self, *args, **options):
10
+ stmt1 = "one"
11
+ stmt2 = "two"
12
+ stmt3 = "three"
13
+ stmt4 = "four"
14
+ stmt5 = "five"
15
+ stmt6 = "six"
16
+
17
+ # DEBUG check on statement 8 (beyond first 5)
18
+ if not settings.DEBUG:
19
+ raise CommandError("This command can only be run in DEBUG mode")
20
+
21
+ print("Running in DEBUG mode")
22
+ return "Success"
@@ -0,0 +1,15 @@
1
+ """Management command with positive DEBUG check - EDGE CASE: check line 186-187 logic"""
2
+ from django.conf import settings
3
+ from django.core.management.base import BaseCommand, CommandError
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with positive DEBUG check"
8
+
9
+ def handle(self, *args, **options):
10
+ if settings.DEBUG:
11
+ print("Running in DEBUG mode")
12
+ else:
13
+ raise CommandError("This command can only be run in DEBUG mode")
14
+
15
+ return "Success"
@@ -0,0 +1,14 @@
1
+ """Management command with DEBUG and other condition - EDGE CASE: might not be caught"""
2
+ from django.conf import settings
3
+ from django.core.management.base import BaseCommand, CommandError
4
+
5
+
6
+ class Command(BaseCommand):
7
+ help = "Test command with DEBUG AND condition"
8
+
9
+ def handle(self, *args, **options):
10
+ if not settings.DEBUG and True:
11
+ raise CommandError("This command can only be run in DEBUG mode")
12
+
13
+ print("Running")
14
+ return "Success"
@@ -0,0 +1,10 @@
1
+ """Regular file NOT in management/commands/ - should NOT be checked"""
2
+ from django.core.management.base import BaseCommand
3
+
4
+
5
+ class Command(BaseCommand):
6
+ help = "This is not in management/commands/ directory"
7
+
8
+ def handle(self, *args, **options):
9
+ print("No DEBUG check needed")
10
+ return "Success"
@@ -0,0 +1,8 @@
1
+ """Test file with simple @skip decorator - SHOULD BE CAUGHT"""
2
+ import pytest
3
+ from unittest import skip
4
+
5
+
6
+ @skip
7
+ def test_something():
8
+ assert True
@@ -0,0 +1,8 @@
1
+ """Test file with @pytest.mark.skipif - SHOULD BE CAUGHT"""
2
+ import pytest
3
+ import sys
4
+
5
+
6
+ @pytest.mark.skipif(sys.platform == "win32", reason="Windows not supported")
7
+ def test_something():
8
+ assert True
@@ -0,0 +1,8 @@
1
+ """Test file with @unittest.skipIf - SHOULD BE CAUGHT"""
2
+ import unittest
3
+
4
+
5
+ class TestCase(unittest.TestCase):
6
+ @unittest.skipIf(True, "Reason")
7
+ def test_something(self):
8
+ self.assertTrue(True)
@@ -0,0 +1,8 @@
1
+ """Test file with @skip() with parentheses - SHOULD BE CAUGHT"""
2
+ import pytest
3
+ from unittest import skip
4
+
5
+
6
+ @skip("test reason")
7
+ def test_something():
8
+ assert True
@@ -0,0 +1,7 @@
1
+ """Test file with @pytest.mark.xfail - should NOT be caught (allowed)"""
2
+ import pytest
3
+
4
+
5
+ @pytest.mark.xfail(reason="Known bug")
6
+ def test_something():
7
+ assert False
@@ -0,0 +1,11 @@
1
+ """Test file with custom @skip_on_windows - should NOT be caught"""
2
+ import pytest
3
+
4
+
5
+ def skip_on_windows(func):
6
+ return func
7
+
8
+
9
+ @skip_on_windows
10
+ def test_something():
11
+ assert True
@@ -0,0 +1,8 @@
1
+ """Test file with @Skip (capital S) - EDGE CASE: might slip through"""
2
+ import unittest
3
+ from unittest import skip as Skip
4
+
5
+
6
+ @Skip
7
+ def test_something():
8
+ assert True
@@ -0,0 +1,7 @@
1
+ """Test file with @unittest.skipUnless - SHOULD BE CAUGHT"""
2
+ import unittest
3
+
4
+
5
+ @unittest.skipUnless(False, "Reason")
6
+ def test_something():
7
+ assert True
@@ -0,0 +1,7 @@
1
+ """Test file with @pytest.mark.skip (no args) - SHOULD BE CAUGHT"""
2
+ import pytest
3
+
4
+
5
+ @pytest.mark.skip
6
+ def test_something():
7
+ assert True
@@ -0,0 +1,45 @@
1
+ """Test file with async functions to verify AsyncFunctionDef support."""
2
+
3
+ import asyncio
4
+
5
+
6
+ def dummy_decorator(func):
7
+ """Simple decorator for testing."""
8
+ return func
9
+
10
+
11
+ async def async_func_with_decorator_spacing():
12
+ """Async function - should pass."""
13
+ await asyncio.sleep(0)
14
+
15
+
16
+ @dummy_decorator
17
+ async def async_func_with_empty_line_after_decorator():
18
+ """Should fail - empty line after decorator."""
19
+ await asyncio.sleep(0)
20
+
21
+
22
+ async def async_func_one():
23
+ """First async function."""
24
+ await asyncio.sleep(0)
25
+
26
+
27
+ async def async_func_two():
28
+ """Second async function - should pass with single empty line."""
29
+ await asyncio.sleep(0)
30
+
31
+
32
+ async def async_func_three():
33
+ """Third async function - should pass."""
34
+ await asyncio.sleep(0)
35
+
36
+
37
+
38
+ async def async_func_four():
39
+ """Fourth async function - should fail with multiple empty lines."""
40
+ await asyncio.sleep(0)
41
+
42
+
43
+ def regular_func():
44
+ """Regular function between async functions."""
45
+ pass
@@ -0,0 +1,7 @@
1
+ import { PureComponent } from 'react';
2
+
3
+ class OptimizedList extends PureComponent {
4
+ render() {
5
+ return <ul>Items</ul>;
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ class Dashboard extends React.PureComponent {
4
+ render() {
5
+ return <div>Dashboard</div>;
6
+ }
7
+ }
@@ -0,0 +1,295 @@
1
+ """Tests for git and GitHub validation checks."""
2
+
3
+ import json
4
+ import subprocess
5
+ from pathlib import Path
6
+ from typing import List
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pytest
10
+
11
+ from git_checks import (
12
+ Violation,
13
+ check_single_commit_when_pr_exists,
14
+ check_draft_pr_state,
15
+ main,
16
+ )
17
+
18
+
19
+ class TestSingleCommitWhenPrExists:
20
+ """Test that PR branches have exactly 1 commit ahead of base."""
21
+
22
+ @patch("git_checks.subprocess.run")
23
+ def test_no_pr_returns_empty(self, mock_run: MagicMock) -> None:
24
+ """When no PR exists, check should return empty list."""
25
+ mock_run.return_value = MagicMock(returncode=0, stdout="[]", stderr="")
26
+
27
+ violations = check_single_commit_when_pr_exists()
28
+
29
+ assert violations == []
30
+
31
+ @patch("git_checks.subprocess.run")
32
+ def test_single_commit_ahead_passes(self, mock_run: MagicMock) -> None:
33
+ """Exactly 1 commit ahead should pass."""
34
+ mock_run.side_effect = [
35
+ MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
36
+ MagicMock(returncode=0, stdout="1", stderr=""),
37
+ ]
38
+
39
+ violations = check_single_commit_when_pr_exists()
40
+
41
+ assert violations == []
42
+
43
+ @patch("git_checks.subprocess.run")
44
+ def test_zero_commits_ahead_fails(self, mock_run: MagicMock) -> None:
45
+ """Zero commits ahead should fail."""
46
+ mock_run.side_effect = [
47
+ MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
48
+ MagicMock(returncode=0, stdout="0", stderr=""),
49
+ ]
50
+
51
+ violations = check_single_commit_when_pr_exists()
52
+
53
+ assert len(violations) == 1
54
+ assert violations[0].file == ""
55
+ assert violations[0].line == 0
56
+ assert "exactly 1 commit" in violations[0].message
57
+ assert "0 commits" in violations[0].message
58
+
59
+ @patch("git_checks.subprocess.run")
60
+ def test_multiple_commits_ahead_fails(self, mock_run: MagicMock) -> None:
61
+ """More than 1 commit ahead should fail."""
62
+ mock_run.side_effect = [
63
+ MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
64
+ MagicMock(returncode=0, stdout="3", stderr=""),
65
+ ]
66
+
67
+ violations = check_single_commit_when_pr_exists()
68
+
69
+ assert len(violations) == 1
70
+ assert "exactly 1 commit" in violations[0].message
71
+ assert "3 commits" in violations[0].message
72
+
73
+ @patch("git_checks.subprocess.run")
74
+ def test_gh_cli_not_available_returns_empty(self, mock_run: MagicMock) -> None:
75
+ """When gh CLI not available, should return empty (warning, not failure)."""
76
+ mock_run.side_effect = FileNotFoundError("gh not found")
77
+
78
+ violations = check_single_commit_when_pr_exists()
79
+
80
+ assert violations == []
81
+
82
+ @patch("git_checks.subprocess.run")
83
+ def test_git_not_available_returns_empty(self, mock_run: MagicMock) -> None:
84
+ """When git not available, should return empty."""
85
+ mock_run.side_effect = [
86
+ MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
87
+ FileNotFoundError("git not found"),
88
+ ]
89
+
90
+ violations = check_single_commit_when_pr_exists()
91
+
92
+ assert violations == []
93
+
94
+ @patch("git_checks.subprocess.run")
95
+ def test_extracts_base_branch_from_pr_info(self, mock_run: MagicMock) -> None:
96
+ """Should extract base branch name from gh pr list JSON output."""
97
+ mock_run.side_effect = [
98
+ MagicMock(returncode=0, stdout='[{"baseRefName": "develop", "number": 123}]', stderr=""),
99
+ MagicMock(returncode=0, stdout="2", stderr=""),
100
+ ]
101
+
102
+ violations = check_single_commit_when_pr_exists()
103
+
104
+ assert len(violations) == 1
105
+ mock_run.assert_any_call(
106
+ ["git", "rev-list", "--count", "develop..HEAD"],
107
+ capture_output=True,
108
+ text=True,
109
+ check=True,
110
+ timeout=30,
111
+ )
112
+
113
+ @patch("git_checks.subprocess.run")
114
+ def test_non_numeric_commit_count_returns_empty(self, mock_run: MagicMock) -> None:
115
+ """When git rev-list returns non-numeric output, should return empty."""
116
+ mock_run.side_effect = [
117
+ MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
118
+ MagicMock(returncode=0, stdout="not a number\n", stderr=""),
119
+ ]
120
+
121
+ violations = check_single_commit_when_pr_exists()
122
+
123
+ assert violations == []
124
+
125
+ @patch("git_checks.subprocess.run")
126
+ def test_gh_timeout_returns_empty(self, mock_run: MagicMock) -> None:
127
+ """When gh CLI times out, should return empty (warning, not failure)."""
128
+ mock_run.side_effect = subprocess.TimeoutExpired(cmd=["gh", "pr", "list"], timeout=30)
129
+
130
+ violations = check_single_commit_when_pr_exists()
131
+
132
+ assert violations == []
133
+
134
+ @patch("git_checks.subprocess.run")
135
+ def test_git_timeout_returns_empty(self, mock_run: MagicMock) -> None:
136
+ """When git times out, should return empty (warning, not failure)."""
137
+ mock_run.side_effect = [
138
+ MagicMock(returncode=0, stdout='[{"baseRefName": "main", "number": 123}]', stderr=""),
139
+ subprocess.TimeoutExpired(cmd=["git", "rev-list"], timeout=30),
140
+ ]
141
+
142
+ violations = check_single_commit_when_pr_exists()
143
+
144
+ assert violations == []
145
+
146
+
147
+ class TestDraftPrState:
148
+ """Test that PR is in draft state when pushing review fixes."""
149
+
150
+ @patch("git_checks.subprocess.run")
151
+ def test_no_pr_returns_empty(self, mock_run: MagicMock) -> None:
152
+ """When no PR exists, check should return empty list."""
153
+ mock_run.return_value = MagicMock(returncode=0, stdout="[]", stderr="")
154
+
155
+ violations = check_draft_pr_state()
156
+
157
+ assert violations == []
158
+
159
+ @patch("git_checks.subprocess.run")
160
+ def test_draft_pr_passes(self, mock_run: MagicMock) -> None:
161
+ """Draft PR should pass."""
162
+ mock_run.return_value = MagicMock(
163
+ returncode=0,
164
+ stdout='[{"number": 123, "isDraft": true}]',
165
+ stderr=""
166
+ )
167
+
168
+ violations = check_draft_pr_state()
169
+
170
+ assert violations == []
171
+
172
+ @patch("git_checks.subprocess.run")
173
+ def test_non_draft_pr_fails(self, mock_run: MagicMock) -> None:
174
+ """Non-draft PR should fail."""
175
+ mock_run.return_value = MagicMock(
176
+ returncode=0,
177
+ stdout='[{"number": 123, "isDraft": false}]',
178
+ stderr=""
179
+ )
180
+
181
+ violations = check_draft_pr_state()
182
+
183
+ assert len(violations) == 1
184
+ assert violations[0].file == ""
185
+ assert violations[0].line == 0
186
+ assert "draft" in violations[0].message.lower()
187
+ assert "gh pr ready --undo" in violations[0].message
188
+
189
+ @patch("git_checks.subprocess.run")
190
+ def test_gh_cli_not_available_returns_empty(self, mock_run: MagicMock) -> None:
191
+ """When gh CLI not available, should return empty (warning, not failure)."""
192
+ mock_run.side_effect = FileNotFoundError("gh not found")
193
+
194
+ violations = check_draft_pr_state()
195
+
196
+ assert violations == []
197
+
198
+ @patch("git_checks.subprocess.run")
199
+ def test_gh_timeout_returns_empty(self, mock_run: MagicMock) -> None:
200
+ """When gh CLI times out, should return empty (warning, not failure)."""
201
+ mock_run.side_effect = subprocess.TimeoutExpired(cmd=["gh", "pr", "list"], timeout=30)
202
+
203
+ violations = check_draft_pr_state()
204
+
205
+ assert violations == []
206
+
207
+
208
+ class TestMain:
209
+ """Test main function integration."""
210
+
211
+ @patch("git_checks.check_single_commit_when_pr_exists")
212
+ @patch("git_checks.check_draft_pr_state")
213
+ def test_main_no_violations_exits_zero(
214
+ self,
215
+ mock_draft: MagicMock,
216
+ mock_commit: MagicMock,
217
+ capsys,
218
+ ) -> None:
219
+ """main() should exit 0 when no violations found."""
220
+ mock_commit.return_value = []
221
+ mock_draft.return_value = []
222
+
223
+ with pytest.raises(SystemExit) as exc_info:
224
+ main()
225
+
226
+ assert exc_info.value.code == 0
227
+ captured = capsys.readouterr()
228
+ assert captured.out == ""
229
+
230
+ @patch("git_checks.check_single_commit_when_pr_exists")
231
+ @patch("git_checks.check_draft_pr_state")
232
+ def test_main_with_violations_exits_one(
233
+ self,
234
+ mock_draft: MagicMock,
235
+ mock_commit: MagicMock,
236
+ capsys,
237
+ ) -> None:
238
+ """main() should exit 1 and print violations when found."""
239
+ mock_commit.return_value = [
240
+ Violation(file="", line=0, message="Branch has 3 commits ahead")
241
+ ]
242
+ mock_draft.return_value = []
243
+
244
+ with pytest.raises(SystemExit) as exc_info:
245
+ main()
246
+
247
+ assert exc_info.value.code == 1
248
+ captured = capsys.readouterr()
249
+ assert "Branch has 3 commits ahead" in captured.out
250
+
251
+ @patch("git_checks.check_single_commit_when_pr_exists")
252
+ @patch("git_checks.check_draft_pr_state")
253
+ def test_main_prints_violations_without_file_line(
254
+ self,
255
+ mock_draft: MagicMock,
256
+ mock_commit: MagicMock,
257
+ capsys,
258
+ ) -> None:
259
+ """main() should print git violations without file:line: prefix."""
260
+ mock_commit.return_value = []
261
+ mock_draft.return_value = [
262
+ Violation(file="", line=0, message="PR must be in draft state")
263
+ ]
264
+
265
+ with pytest.raises(SystemExit) as exc_info:
266
+ main()
267
+
268
+ assert exc_info.value.code == 1
269
+ captured = capsys.readouterr()
270
+ assert captured.out == "PR must be in draft state\n"
271
+ assert ":0:" not in captured.out
272
+
273
+ @patch("git_checks.check_single_commit_when_pr_exists")
274
+ @patch("git_checks.check_draft_pr_state")
275
+ def test_main_prints_all_violations(
276
+ self,
277
+ mock_draft: MagicMock,
278
+ mock_commit: MagicMock,
279
+ capsys,
280
+ ) -> None:
281
+ """main() should print all violations."""
282
+ mock_commit.return_value = [
283
+ Violation(file="", line=0, message="Branch has 2 commits")
284
+ ]
285
+ mock_draft.return_value = [
286
+ Violation(file="", line=0, message="PR not in draft")
287
+ ]
288
+
289
+ with pytest.raises(SystemExit) as exc_info:
290
+ main()
291
+
292
+ assert exc_info.value.code == 1
293
+ captured = capsys.readouterr()
294
+ assert "Branch has 2 commits" in captured.out
295
+ assert "PR not in draft" in captured.out
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+
3
+ function GoodComponent() {
4
+ return <div>Good</div>;
5
+ }