claude-dev-env 1.34.0 → 1.35.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 (44) hide show
  1. package/agents/docs-agent.md +1 -1
  2. package/agents/project-docs-analyzer.md +0 -1
  3. package/agents/skill-to-agent-converter.md +0 -1
  4. package/commands/initialize.md +0 -1
  5. package/commands/readability-review.md +4 -4
  6. package/commands/review-plan.md +2 -4
  7. package/commands/stubcheck.md +1 -2
  8. package/hooks/blocking/code_rules_enforcer.py +250 -36
  9. package/hooks/blocking/test_code_rules_enforcer.py +91 -39
  10. package/hooks/blocking/test_code_rules_enforcer_annotations.py +97 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +137 -0
  12. package/hooks/blocking/test_code_rules_enforcer_config_path.py +0 -20
  13. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +0 -18
  14. package/hooks/blocking/test_code_rules_enforcer_existence_checks.py +0 -18
  15. package/hooks/blocking/test_code_rules_enforcer_inline_literal_collections.py +155 -0
  16. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +110 -0
  17. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +0 -13
  18. package/hooks/blocking/test_code_rules_enforcer_skip_decorators.py +0 -26
  19. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +234 -0
  20. package/package.json +1 -1
  21. package/skills/bugteam/PROMPTS.md +0 -39
  22. package/skills/bugteam/SKILL.md +17 -35
  23. package/skills/bugteam/reference/copilot-gap-analysis.md +12 -0
  24. package/skills/pr-converge/SKILL.md +185 -167
  25. package/agents/agent-writer.md +0 -157
  26. package/agents/config-centralizer.md +0 -686
  27. package/agents/config-extraction-agent.md +0 -225
  28. package/agents/doc-orchestrator.md +0 -47
  29. package/agents/docx-agent.md +0 -211
  30. package/agents/magic-value-eliminator-agent.md +0 -72
  31. package/agents/mandatory-agent-workflow-agent.md +0 -88
  32. package/agents/parallel-workflow-coordinator.md +0 -779
  33. package/agents/pdf-agent.md +0 -302
  34. package/agents/project-context-loader.md +0 -238
  35. package/agents/readability-review-agent.md +0 -76
  36. package/agents/refactoring-specialist.md +0 -69
  37. package/agents/right-sized-engineer.md +0 -129
  38. package/agents/session-continuity-manager.md +0 -53
  39. package/agents/stub-detector-agent.md +0 -140
  40. package/agents/tdd-test-writer.md +0 -62
  41. package/agents/test-data-builder.md +0 -68
  42. package/agents/tooling-builder.md +0 -78
  43. package/agents/validation-expert.md +0 -71
  44. package/agents/xlsx-agent.md +0 -169
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import importlib.util
5
+
6
+ ENFORCER_PATH = Path(__file__).resolve().parent / "code_rules_enforcer.py"
7
+ specification = importlib.util.spec_from_file_location(
8
+ "code_rules_enforcer", ENFORCER_PATH
9
+ )
10
+ code_rules_enforcer = importlib.util.module_from_spec(specification)
11
+ specification.loader.exec_module(code_rules_enforcer)
12
+
13
+ PRODUCTION_FILE_PATH = "packages/app/services/foo.py"
14
+ TEST_FILE_PATH = "packages/app/tests/test_foo.py"
15
+
16
+
17
+ def test_should_flag_parameter_without_annotation() -> None:
18
+ source = "def consume(value) -> None:\n return None\n"
19
+ issues = code_rules_enforcer.check_parameter_annotations(
20
+ source, PRODUCTION_FILE_PATH
21
+ )
22
+ assert any("value" in each_issue for each_issue in issues), (
23
+ f"Expected unannotated parameter flagged, got: {issues}"
24
+ )
25
+
26
+
27
+ def test_should_not_flag_annotated_parameters() -> None:
28
+ source = (
29
+ "def consume(value: int, label: str = 'default') -> None:\n return None\n"
30
+ )
31
+ issues = code_rules_enforcer.check_parameter_annotations(
32
+ source, PRODUCTION_FILE_PATH
33
+ )
34
+ assert issues == [], f"Expected no issues for annotated params, got: {issues}"
35
+
36
+
37
+ def test_should_exempt_self_and_cls_parameters() -> None:
38
+ source = (
39
+ "class Foo:\n"
40
+ " def method(self, value: int) -> None:\n"
41
+ " return None\n"
42
+ " @classmethod\n"
43
+ " def factory(cls, value: int) -> 'Foo':\n"
44
+ " return cls()\n"
45
+ )
46
+ issues = code_rules_enforcer.check_parameter_annotations(
47
+ source, PRODUCTION_FILE_PATH
48
+ )
49
+ assert issues == [], (
50
+ f"self/cls must be exempt from annotation requirement, got: {issues}"
51
+ )
52
+
53
+
54
+ def test_should_flag_class_method_parameter_without_annotation() -> None:
55
+ source = "class Foo:\n def method(self, value) -> None:\n return None\n"
56
+ issues = code_rules_enforcer.check_parameter_annotations(
57
+ source, PRODUCTION_FILE_PATH
58
+ )
59
+ assert any("value" in each_issue for each_issue in issues), (
60
+ f"Expected method param flagged, got: {issues}"
61
+ )
62
+
63
+
64
+ def test_should_skip_parameter_check_in_test_files() -> None:
65
+ source = "def consume(value) -> None:\n return None\n"
66
+ issues = code_rules_enforcer.check_parameter_annotations(source, TEST_FILE_PATH)
67
+ assert issues == [], f"Test files must be exempt, got: {issues}"
68
+
69
+
70
+ def test_should_flag_function_without_return_annotation() -> None:
71
+ source = "def fetch(url: str):\n return url\n"
72
+ issues = code_rules_enforcer.check_return_annotations(source, PRODUCTION_FILE_PATH)
73
+ assert any("fetch" in each_issue for each_issue in issues), (
74
+ f"Expected function without return type flagged, got: {issues}"
75
+ )
76
+
77
+
78
+ def test_should_not_flag_function_with_return_annotation() -> None:
79
+ source = "def fetch(url: str) -> str:\n return url\n"
80
+ issues = code_rules_enforcer.check_return_annotations(source, PRODUCTION_FILE_PATH)
81
+ assert issues == [], f"Function with return type must not be flagged, got: {issues}"
82
+
83
+
84
+ def test_should_flag_async_function_without_return_annotation() -> None:
85
+ source = "async def fetch(url: str):\n return url\n"
86
+ issues = code_rules_enforcer.check_return_annotations(source, PRODUCTION_FILE_PATH)
87
+ assert any("fetch" in each_issue for each_issue in issues), (
88
+ f"Expected async function without return type flagged, got: {issues}"
89
+ )
90
+
91
+
92
+ def test_should_skip_return_check_in_test_files() -> None:
93
+ source = "def fetch(url: str):\n return url\n"
94
+ issues = code_rules_enforcer.check_return_annotations(source, TEST_FILE_PATH)
95
+ assert issues == [], f"Test files must be exempt, got: {issues}"
96
+
97
+
@@ -0,0 +1,137 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import importlib.util
5
+
6
+ ENFORCER_PATH = Path(__file__).resolve().parent / "code_rules_enforcer.py"
7
+ specification = importlib.util.spec_from_file_location(
8
+ "code_rules_enforcer", ENFORCER_PATH
9
+ )
10
+ code_rules_enforcer = importlib.util.module_from_spec(specification)
11
+ specification.loader.exec_module(code_rules_enforcer)
12
+
13
+ PRODUCTION_FILE_PATH = "packages/app/services/foo.py"
14
+
15
+
16
+ def test_should_flag_bare_set_parameter_without_all_prefix() -> None:
17
+ source = "def consume(numbers: set[int]) -> None:\n return None\n"
18
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
19
+ assert any("numbers" in each_issue for each_issue in issues), (
20
+ f"Expected bare set[int] parameter flagged, got: {issues}"
21
+ )
22
+
23
+
24
+ def test_should_flag_pep604_union_set_with_none_right() -> None:
25
+ source = "def consume(numbers: set[int] | None = None) -> None:\n return None\n"
26
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
27
+ assert any("numbers" in each_issue for each_issue in issues), (
28
+ f"Expected set[int] | None parameter flagged, got: {issues}"
29
+ )
30
+
31
+
32
+ def test_should_flag_pep604_union_set_with_none_left() -> None:
33
+ source = "def consume(numbers: None | set[int] = None) -> None:\n return None\n"
34
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
35
+ assert any("numbers" in each_issue for each_issue in issues), (
36
+ f"Expected None | set[int] parameter flagged, got: {issues}"
37
+ )
38
+
39
+
40
+ def test_should_flag_optional_set_parameter() -> None:
41
+ source = (
42
+ "from typing import Optional\n"
43
+ "\n"
44
+ "def consume(numbers: Optional[set[int]] = None) -> None:\n"
45
+ " return None\n"
46
+ )
47
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
48
+ assert any("numbers" in each_issue for each_issue in issues), (
49
+ f"Expected Optional[set[int]] parameter flagged, got: {issues}"
50
+ )
51
+
52
+
53
+ def test_should_flag_union_set_with_none_parameter() -> None:
54
+ source = (
55
+ "from typing import Union\n"
56
+ "\n"
57
+ "def consume(numbers: Union[set[int], None] = None) -> None:\n"
58
+ " return None\n"
59
+ )
60
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
61
+ assert any("numbers" in each_issue for each_issue in issues), (
62
+ f"Expected Union[set[int], None] parameter flagged, got: {issues}"
63
+ )
64
+
65
+
66
+ def test_should_flag_pep604_union_dict_parameter() -> None:
67
+ source = (
68
+ "from pathlib import Path\n"
69
+ "\n"
70
+ "def consume(per_path: dict[Path, set[int]] | None = None) -> None:\n"
71
+ " return None\n"
72
+ )
73
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
74
+ assert any("per_path" in each_issue for each_issue in issues), (
75
+ f"Expected dict[..] | None parameter flagged, got: {issues}"
76
+ )
77
+
78
+
79
+ def test_should_not_flag_pep604_union_when_param_has_all_prefix() -> None:
80
+ source = (
81
+ "def consume(all_numbers: set[int] | None = None) -> None:\n return None\n"
82
+ )
83
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
84
+ assert not any("all_numbers" in each_issue for each_issue in issues), (
85
+ f"all_-prefixed parameter must not be flagged, got: {issues}"
86
+ )
87
+
88
+
89
+ def test_should_not_flag_optional_when_param_uses_x_by_y_pattern() -> None:
90
+ source = (
91
+ "from typing import Optional\n"
92
+ "\n"
93
+ "def consume(price_by_product: Optional[dict[str, int]] = None) -> None:\n"
94
+ " return None\n"
95
+ )
96
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
97
+ assert not any("price_by_product" in each_issue for each_issue in issues), (
98
+ f"X_by_Y-pattern parameter must not be flagged, got: {issues}"
99
+ )
100
+
101
+
102
+ def test_should_flag_qualified_typing_optional_set_parameter() -> None:
103
+ source = (
104
+ "import typing\n"
105
+ "\n"
106
+ "def consume(numbers: typing.Optional[set[int]] = None) -> None:\n"
107
+ " return None\n"
108
+ )
109
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
110
+ assert any("numbers" in each_issue for each_issue in issues), (
111
+ f"Expected typing.Optional[set[int]] (ast.Attribute outer subscript) flagged, got: {issues}"
112
+ )
113
+
114
+
115
+ def test_should_flag_qualified_typing_union_with_none_parameter() -> None:
116
+ source = (
117
+ "import typing\n"
118
+ "\n"
119
+ "def consume(numbers: typing.Union[set[int], None] = None) -> None:\n"
120
+ " return None\n"
121
+ )
122
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
123
+ assert any("numbers" in each_issue for each_issue in issues), (
124
+ f"Expected typing.Union[set[int], None] (ast.Attribute outer subscript) flagged, got: {issues}"
125
+ )
126
+
127
+
128
+ def test_should_flag_module_level_qualified_typing_optional_constant() -> None:
129
+ source = (
130
+ "import typing\n"
131
+ "\n"
132
+ "RAW_NUMBERS: typing.Optional[set[int]] = None\n"
133
+ )
134
+ issues = code_rules_enforcer.check_collection_prefix(source, PRODUCTION_FILE_PATH)
135
+ assert any("RAW_NUMBERS" in each_issue for each_issue in issues), (
136
+ f"Expected typing.Optional[set[int]] module-level constant flagged, got: {issues}"
137
+ )
@@ -83,26 +83,6 @@ def test_should_produce_blocking_for_module_level_upper_snake_outside_config() -
83
83
  assert any("MAX_RETRIES" in issue for issue in blocking_issues)
84
84
 
85
85
 
86
- def test_should_include_file_path_in_advisory_stderr_output() -> None:
87
- source = (
88
- "def fetch_data():\n"
89
- " MAX_RETRIES = 3\n"
90
- " for attempt in range(MAX_RETRIES):\n"
91
- " pass\n"
92
- )
93
- captured_stderr = io.StringIO()
94
- old_stderr = sys.stderr
95
- sys.stderr = captured_stderr
96
- try:
97
- code_rules_enforcer.validate_content(source, PRODUCTION_FILE_PATH, "")
98
- finally:
99
- sys.stderr = old_stderr
100
- advisory_output = captured_stderr.getvalue()
101
- assert PRODUCTION_FILE_PATH in advisory_output, (
102
- f"Advisory stderr must include the file path; got: {advisory_output!r}"
103
- )
104
-
105
-
106
86
  def test_should_produce_stable_ordering_sorted_by_line_number() -> None:
107
87
  source = (
108
88
  "ALPHA_CONSTANT = 1\n"
@@ -110,21 +110,3 @@ def test_should_not_flag_non_test_function() -> None:
110
110
  assert issues == [], f"Expected no issues for non-test function, got: {issues}"
111
111
 
112
112
 
113
- def test_stops_at_max_issues_per_check() -> None:
114
- source = (
115
- "ALPHA = 1\nBETA = 2\nGAMMA = 3\nDELTA = 4\nEPSILON = 5\n"
116
- "\n"
117
- "def test_alpha() -> None:\n assert ALPHA == 1\n"
118
- "\n"
119
- "def test_beta() -> None:\n assert BETA == 2\n"
120
- "\n"
121
- "def test_gamma() -> None:\n assert GAMMA == 3\n"
122
- "\n"
123
- "def test_delta() -> None:\n assert DELTA == 4\n"
124
- "\n"
125
- "def test_epsilon() -> None:\n assert EPSILON == 5\n"
126
- )
127
- issues = code_rules_enforcer.check_constant_equality_tests(source, TEST_FILE_PATH)
128
- assert len(issues) == code_rules_enforcer.MAX_ISSUES_PER_CHECK, (
129
- f"Expected exactly MAX_ISSUES_PER_CHECK issues, got {len(issues)}: {issues}"
130
- )
@@ -101,24 +101,6 @@ def test_should_not_flag_non_test_function_with_existence_check() -> None:
101
101
  assert issues == [], f"Expected no issues for non-test function, got: {issues}"
102
102
 
103
103
 
104
- def test_stops_at_max_issues_per_check() -> None:
105
- source = (
106
- "def test_one() -> None:\n assert callable(func_one)\n"
107
- "\n"
108
- "def test_two() -> None:\n assert callable(func_two)\n"
109
- "\n"
110
- "def test_three() -> None:\n assert callable(func_three)\n"
111
- "\n"
112
- "def test_four() -> None:\n assert callable(func_four)\n"
113
- "\n"
114
- "def test_five() -> None:\n assert callable(func_five)\n"
115
- )
116
- issues = code_rules_enforcer.check_existence_check_tests(source, TEST_FILE_PATH)
117
- assert len(issues) == code_rules_enforcer.MAX_ISSUES_PER_CHECK, (
118
- f"Expected exactly MAX_ISSUES_PER_CHECK issues, got {len(issues)}: {issues}"
119
- )
120
-
121
-
122
104
  def test_should_not_flag_outer_test_when_nested_helper_contains_existence_check() -> None:
123
105
  source = (
124
106
  "def test_outer_behavior() -> None:\n"
@@ -0,0 +1,155 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import importlib.util
5
+
6
+ ENFORCER_PATH = Path(__file__).resolve().parent / "code_rules_enforcer.py"
7
+ specification = importlib.util.spec_from_file_location(
8
+ "code_rules_enforcer", ENFORCER_PATH
9
+ )
10
+ code_rules_enforcer = importlib.util.module_from_spec(specification)
11
+ specification.loader.exec_module(code_rules_enforcer)
12
+
13
+ PRODUCTION_FILE_PATH = "packages/app/services/foo.py"
14
+ TEST_FILE_PATH = "packages/app/tests/test_foo.py"
15
+ CONFIG_FILE_PATH = "packages/app/config/constants.py"
16
+
17
+
18
+ def test_should_flag_set_literal_with_three_string_constants_in_function_body() -> None:
19
+ source = (
20
+ "def is_known(value: str) -> bool:\n"
21
+ " return value in {'true', 'false', 'none'}\n"
22
+ )
23
+ issues = code_rules_enforcer.check_inline_literal_collections(
24
+ source, PRODUCTION_FILE_PATH
25
+ )
26
+ assert len(issues) == 1, f"Expected 3-element set flagged, got: {issues}"
27
+
28
+
29
+ def test_should_flag_list_literal_with_five_string_constants_in_function_body() -> None:
30
+ source = (
31
+ "def is_code_path(suffix: str) -> bool:\n"
32
+ " return suffix in ['.py', '.js', '.ts', '.tsx', '.jsx']\n"
33
+ )
34
+ issues = code_rules_enforcer.check_inline_literal_collections(
35
+ source, PRODUCTION_FILE_PATH
36
+ )
37
+ assert len(issues) == 1, f"Expected 5-element list flagged, got: {issues}"
38
+
39
+
40
+ def test_should_not_flag_two_element_set_literal() -> None:
41
+ source = "def is_binary(value: str) -> bool:\n return value in {'on', 'off'}\n"
42
+ issues = code_rules_enforcer.check_inline_literal_collections(
43
+ source, PRODUCTION_FILE_PATH
44
+ )
45
+ assert issues == [], f"2-element literal must not be flagged, got: {issues}"
46
+
47
+
48
+ def test_should_not_flag_literal_with_variable_references() -> None:
49
+ source = (
50
+ "def consume(a: int, b: int, c: int, d: int) -> list[int]:\n"
51
+ " return [a, b, c, d]\n"
52
+ )
53
+ issues = code_rules_enforcer.check_inline_literal_collections(
54
+ source, PRODUCTION_FILE_PATH
55
+ )
56
+ assert issues == [], (
57
+ f"Variable-reference literal must not be flagged, got: {issues}"
58
+ )
59
+
60
+
61
+ def test_should_not_flag_module_level_literal() -> None:
62
+ source = "ALL_VALID_KEYS = {'true', 'false', 'none'}\n"
63
+ issues = code_rules_enforcer.check_inline_literal_collections(
64
+ source, PRODUCTION_FILE_PATH
65
+ )
66
+ assert issues == [], (
67
+ f"Module-level literal must not be flagged by inline-collection check, got: {issues}"
68
+ )
69
+
70
+
71
+ def test_should_skip_in_test_files() -> None:
72
+ source = (
73
+ "def test_something() -> None:\n"
74
+ " keys = {'true', 'false', 'none'}\n"
75
+ " assert 'true' in keys\n"
76
+ )
77
+ issues = code_rules_enforcer.check_inline_literal_collections(
78
+ source, TEST_FILE_PATH
79
+ )
80
+ assert issues == [], f"Test files exempt, got: {issues}"
81
+
82
+
83
+ def test_should_skip_in_config_files() -> None:
84
+ source = "def known_keys() -> set[str]:\n return {'true', 'false', 'none'}\n"
85
+ issues = code_rules_enforcer.check_inline_literal_collections(
86
+ source, CONFIG_FILE_PATH
87
+ )
88
+ assert issues == [], f"Config files exempt (they own such tables), got: {issues}"
89
+
90
+
91
+ def test_should_flag_multiple_inline_collections_in_same_function() -> None:
92
+ source = (
93
+ "def consume(value: str) -> bool:\n"
94
+ " return value in {'a', 'b', 'c'} or value in {'x', 'y', 'z'}\n"
95
+ )
96
+ issues = code_rules_enforcer.check_inline_literal_collections(
97
+ source, PRODUCTION_FILE_PATH
98
+ )
99
+ assert len(issues) == 2, f"Expected 2 flagged literals, got: {issues}"
100
+
101
+
102
+ def test_should_not_flag_default_argument_set_literal() -> None:
103
+ source = (
104
+ "def consume(keys: set[str] = {'a', 'b', 'c'}) -> set[str]:\n"
105
+ " return keys\n"
106
+ )
107
+ issues = code_rules_enforcer.check_inline_literal_collections(
108
+ source, PRODUCTION_FILE_PATH
109
+ )
110
+ assert issues == [], (
111
+ f"Default argument value (signature, not body) must not be flagged, got: {issues}"
112
+ )
113
+
114
+
115
+ def test_should_not_flag_default_argument_list_literal() -> None:
116
+ source = (
117
+ "def consume(suffixes: list[str] = ['.py', '.js', '.ts']) -> list[str]:\n"
118
+ " return suffixes\n"
119
+ )
120
+ issues = code_rules_enforcer.check_inline_literal_collections(
121
+ source, PRODUCTION_FILE_PATH
122
+ )
123
+ assert issues == [], (
124
+ f"Default argument list (signature, not body) must not be flagged, got: {issues}"
125
+ )
126
+
127
+
128
+ def test_should_not_flag_default_arg_set_of_nested_function_from_outer_scan() -> None:
129
+ source = (
130
+ "def outer() -> None:\n"
131
+ " def inner(keys: set[str] = {'a', 'b', 'c'}) -> set[str]:\n"
132
+ " return keys\n"
133
+ " return None\n"
134
+ )
135
+ issues = code_rules_enforcer.check_inline_literal_collections(
136
+ source, PRODUCTION_FILE_PATH
137
+ )
138
+ assert issues == [], (
139
+ f"Nested function's default-arg set (signature) must not be flagged from outer scan, got: {issues}"
140
+ )
141
+
142
+
143
+ def test_should_still_flag_set_literal_in_nested_function_body() -> None:
144
+ source = (
145
+ "def outer() -> bool:\n"
146
+ " def inner(value: str) -> bool:\n"
147
+ " return value in {'a', 'b', 'c'}\n"
148
+ " return inner('a')\n"
149
+ )
150
+ issues = code_rules_enforcer.check_inline_literal_collections(
151
+ source, PRODUCTION_FILE_PATH
152
+ )
153
+ assert len(issues) == 1, (
154
+ f"Inner function's body set literal must be flagged exactly once (no duplicate from outer walk), got: {issues}"
155
+ )
@@ -0,0 +1,110 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import importlib.util
5
+
6
+ ENFORCER_PATH = Path(__file__).resolve().parent / "code_rules_enforcer.py"
7
+ specification = importlib.util.spec_from_file_location(
8
+ "code_rules_enforcer", ENFORCER_PATH
9
+ )
10
+ code_rules_enforcer = importlib.util.module_from_spec(specification)
11
+ specification.loader.exec_module(code_rules_enforcer)
12
+
13
+ PRODUCTION_FILE_PATH = "packages/app/services/foo.py"
14
+ TEST_FILE_PATH = "packages/app/tests/test_foo.py"
15
+
16
+
17
+ def test_should_flag_loop_variable_without_each_prefix() -> None:
18
+ source = "def consume() -> None:\n for marker in []:\n return None\n"
19
+ issues = code_rules_enforcer.check_loop_variable_naming(
20
+ source, PRODUCTION_FILE_PATH
21
+ )
22
+ assert any("marker" in each_issue for each_issue in issues), (
23
+ f"Expected 'marker' loop variable flagged, got: {issues}"
24
+ )
25
+
26
+
27
+ def test_should_not_flag_loop_variable_with_each_prefix() -> None:
28
+ source = "def consume() -> None:\n for each_marker in []:\n return None\n"
29
+ issues = code_rules_enforcer.check_loop_variable_naming(
30
+ source, PRODUCTION_FILE_PATH
31
+ )
32
+ assert issues == [], f"each_marker must not be flagged, got: {issues}"
33
+
34
+
35
+ def test_should_exempt_index_letters_i_j_k() -> None:
36
+ source = (
37
+ "def consume() -> None:\n"
38
+ " for i in range(3):\n"
39
+ " for j in range(3):\n"
40
+ " for k in range(3):\n"
41
+ " return None\n"
42
+ )
43
+ issues = code_rules_enforcer.check_loop_variable_naming(
44
+ source, PRODUCTION_FILE_PATH
45
+ )
46
+ assert issues == [], f"i/j/k must be exempt, got: {issues}"
47
+
48
+
49
+ def test_should_flag_bare_each_without_subject() -> None:
50
+ source = "def consume() -> None:\n for each in []:\n return None\n"
51
+ issues = code_rules_enforcer.check_loop_variable_naming(
52
+ source, PRODUCTION_FILE_PATH
53
+ )
54
+ assert any("each" in each_issue for each_issue in issues), (
55
+ f"Expected bare 'each' flagged, got: {issues}"
56
+ )
57
+
58
+
59
+ def test_should_not_flag_tuple_unpacking_targets() -> None:
60
+ source = (
61
+ "def consume() -> None:\n"
62
+ " for key, value in {}.items():\n"
63
+ " return None\n"
64
+ )
65
+ issues = code_rules_enforcer.check_loop_variable_naming(
66
+ source, PRODUCTION_FILE_PATH
67
+ )
68
+ assert issues == [], f"Tuple-unpack targets exempt, got: {issues}"
69
+
70
+
71
+ def test_should_not_flag_list_comprehension_target() -> None:
72
+ source = "def consume() -> None:\n return [x for x in []]\n"
73
+ issues = code_rules_enforcer.check_loop_variable_naming(
74
+ source, PRODUCTION_FILE_PATH
75
+ )
76
+ assert issues == [], f"Comprehension target exempt, got: {issues}"
77
+
78
+
79
+ def test_should_skip_in_test_files() -> None:
80
+ source = "def test_consume() -> None:\n for marker in []:\n return None\n"
81
+ issues = code_rules_enforcer.check_loop_variable_naming(source, TEST_FILE_PATH)
82
+ assert issues == [], f"Test files exempt, got: {issues}"
83
+
84
+
85
+ def test_should_flag_async_for_loop_variable() -> None:
86
+ source = (
87
+ "async def consume() -> None:\n"
88
+ " async for marker in stream():\n"
89
+ " return None\n"
90
+ )
91
+ issues = code_rules_enforcer.check_loop_variable_naming(
92
+ source, PRODUCTION_FILE_PATH
93
+ )
94
+ assert any("marker" in each_issue for each_issue in issues), (
95
+ f"Expected async-for variable flagged, got: {issues}"
96
+ )
97
+
98
+
99
+ def test_should_exempt_underscore_throwaway_loop_variable() -> None:
100
+ source = (
101
+ "def consume(count: int) -> None:\n"
102
+ " for _ in range(count):\n"
103
+ " return None\n"
104
+ )
105
+ issues = code_rules_enforcer.check_loop_variable_naming(
106
+ source, PRODUCTION_FILE_PATH
107
+ )
108
+ assert issues == [], (
109
+ f"Throwaway '_' loop variable must be exempt (Python idiom for 'value intentionally unused'), got: {issues}"
110
+ )
@@ -148,19 +148,6 @@ def test_should_skip_workflow_registry_files() -> None:
148
148
  assert issues == []
149
149
 
150
150
 
151
- def test_should_cap_issues_at_three() -> None:
152
- source = (
153
- "def f() -> None:\n"
154
- " one = True\n"
155
- " two = False\n"
156
- " three = True\n"
157
- " four = False\n"
158
- " five = True\n"
159
- )
160
- issues = check_boolean_naming(source, PRODUCTION_FILE_PATH)
161
- assert len(issues) == 3
162
-
163
-
164
151
  def test_should_not_flag_syntax_error_as_issue() -> None:
165
152
  source = "def f(:\n valid = True\n"
166
153
  issues = check_boolean_naming(source, PRODUCTION_FILE_PATH)
@@ -122,29 +122,3 @@ def test_should_flag_decorator_with_skip_in_name() -> None:
122
122
  )
123
123
 
124
124
 
125
- def test_stops_at_max_issues_per_check() -> None:
126
- source = (
127
- "import pytest\n"
128
- "\n"
129
- "@pytest.mark.skip(reason='a')\n"
130
- "def test_one() -> None:\n"
131
- " pass\n"
132
- "\n"
133
- "@pytest.mark.skip(reason='b')\n"
134
- "def test_two() -> None:\n"
135
- " pass\n"
136
- "\n"
137
- "@pytest.mark.skip(reason='c')\n"
138
- "def test_three() -> None:\n"
139
- " pass\n"
140
- "\n"
141
- "@pytest.mark.skip(reason='d')\n"
142
- "def test_four() -> None:\n"
143
- " pass\n"
144
- "\n"
145
- "@pytest.mark.skip(reason='e')\n"
146
- "def test_five() -> None:\n"
147
- " pass\n"
148
- )
149
- issues = code_rules_enforcer.check_skip_decorators_in_tests(source, TEST_FILE_PATH)
150
- assert len(issues) == code_rules_enforcer.MAX_ISSUES_PER_CHECK