claude-dev-env 1.34.1 → 1.36.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 (148) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/agents/docs-agent.md +1 -1
  3. package/agents/project-docs-analyzer.md +0 -1
  4. package/agents/skill-to-agent-converter.md +0 -1
  5. package/bin/install.mjs +28 -8
  6. package/bin/install.test.mjs +9 -1
  7. package/commands/initialize.md +0 -1
  8. package/commands/readability-review.md +4 -4
  9. package/commands/review-plan.md +2 -4
  10. package/commands/stubcheck.md +1 -2
  11. package/docs/CODE_RULES.md +3 -0
  12. package/docs/agents-md-alignment-plan.md +123 -0
  13. package/hooks/blocking/code_rules_enforcer.py +686 -60
  14. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  15. package/hooks/blocking/test_code_rules_enforcer.py +273 -39
  16. package/hooks/blocking/test_code_rules_enforcer_annotations.py +97 -0
  17. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  18. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  19. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +328 -0
  20. package/hooks/blocking/test_code_rules_enforcer_config_path.py +0 -20
  21. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +33 -11
  22. package/hooks/blocking/test_code_rules_enforcer_existence_checks.py +0 -18
  23. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  24. package/hooks/blocking/test_code_rules_enforcer_inline_literal_collections.py +155 -0
  25. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +194 -0
  26. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -13
  27. package/hooks/blocking/test_code_rules_enforcer_skip_decorators.py +0 -26
  28. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +234 -0
  29. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  30. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  31. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  32. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  33. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  34. package/hooks/config/banned_identifiers_constants.py +24 -0
  35. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  36. package/hooks/config/hook_log_extractor_constants.py +1 -1
  37. package/hooks/config/pre_tool_use_stdin.py +48 -0
  38. package/hooks/config/setup_project_paths_constants.py +4 -0
  39. package/hooks/config/stuttering_check_config.py +14 -0
  40. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  41. package/hooks/config/sys_path_insert_constants.py +4 -0
  42. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  43. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  44. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  45. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  46. package/hooks/config/unused_module_import_constants.py +7 -0
  47. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  48. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  49. package/hooks/git-hooks/config.py +3 -3
  50. package/hooks/git-hooks/test_gate_utils.py +10 -10
  51. package/hooks/mypy.ini +2 -0
  52. package/package.json +1 -1
  53. package/rules/gh-paginate.md +125 -0
  54. package/skills/bugteam/CONSTRAINTS.md +12 -6
  55. package/skills/bugteam/PROMPTS.md +0 -39
  56. package/skills/bugteam/SKILL.md +93 -125
  57. package/skills/bugteam/SKILL_EVALS.md +25 -23
  58. package/skills/bugteam/reference/README.md +2 -0
  59. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  60. package/skills/bugteam/reference/copilot-gap-analysis.md +12 -0
  61. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  62. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  63. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  64. package/skills/bugteam/test_skill_additions.py +13 -4
  65. package/skills/bugteam/test_team_lifecycle.py +94 -0
  66. package/skills/findbugs/SKILL.md +3 -3
  67. package/skills/fixbugs/SKILL.md +4 -4
  68. package/skills/monitor-open-prs/SKILL.md +32 -2
  69. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  70. package/skills/pr-converge/SKILL.md +576 -95
  71. package/skills/pr-converge/scripts/README.md +145 -0
  72. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  73. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  74. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  75. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  76. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  77. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  78. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  79. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  80. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  81. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  82. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  83. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  84. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  85. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  86. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  87. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  88. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  89. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  90. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  91. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  92. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  93. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  94. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  95. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  96. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  97. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  98. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  99. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  100. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  101. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  102. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  103. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  104. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  105. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  106. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  107. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  108. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  109. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  110. package/skills/pr-converge/test_team_lifecycle.py +47 -0
  111. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  112. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  113. package/skills/qbug/SKILL.md +4 -4
  114. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  115. package/skills/resume-review/SKILL.md +261 -0
  116. package/agents/agent-writer.md +0 -157
  117. package/agents/config-centralizer.md +0 -686
  118. package/agents/config-extraction-agent.md +0 -225
  119. package/agents/doc-orchestrator.md +0 -47
  120. package/agents/docx-agent.md +0 -211
  121. package/agents/magic-value-eliminator-agent.md +0 -72
  122. package/agents/mandatory-agent-workflow-agent.md +0 -88
  123. package/agents/parallel-workflow-coordinator.md +0 -779
  124. package/agents/pdf-agent.md +0 -302
  125. package/agents/project-context-loader.md +0 -238
  126. package/agents/readability-review-agent.md +0 -76
  127. package/agents/refactoring-specialist.md +0 -69
  128. package/agents/right-sized-engineer.md +0 -129
  129. package/agents/session-continuity-manager.md +0 -53
  130. package/agents/stub-detector-agent.md +0 -140
  131. package/agents/tdd-test-writer.md +0 -62
  132. package/agents/test-data-builder.md +0 -68
  133. package/agents/tooling-builder.md +0 -78
  134. package/agents/validation-expert.md +0 -71
  135. package/agents/xlsx-agent.md +0 -169
  136. package/skills/bugteam/scripts/README.md +0 -58
  137. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  138. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  139. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  140. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  141. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  142. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  143. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  144. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  145. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  146. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  147. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  148. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -0,0 +1,173 @@
1
+ """Meta-test asserting every check_* function in code_rules_enforcer follows the cap convention.
2
+
3
+ Bot reviewers on PR #232 flagged check_existence_check_tests,
4
+ check_constant_equality_tests, and check_unused_optional_parameters for
5
+ returning unbounded issue lists, which produces a spammy blocking
6
+ payload when a single file has many violations of the same kind.
7
+
8
+ The convention is: every check_* function should either apply an
9
+ explicit cap (the meta-test treats a function as capped when its source
10
+ contains a ``MAX_`` constant name or uses ``itertools.islice`` for bounded
11
+ iteration), or be explicitly listed below as a known-uncapped check
12
+ along with the reason, or appear in VOID_ADVISORY_CHECK_FUNCTION_NAMES
13
+ when the function is annotated ``-> None`` and never contributes issues to the
14
+ blocking payload (stderr-only advisories). New check_* functions added to the
15
+ module without consideration will trip this test.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import importlib.util
21
+ import inspect
22
+ import pathlib
23
+ import sys
24
+
25
+
26
+ _HOOK_DIRECTORY = pathlib.Path(__file__).parent
27
+ if str(_HOOK_DIRECTORY) not in sys.path:
28
+ sys.path.insert(0, str(_HOOK_DIRECTORY))
29
+
30
+ _hook_spec = importlib.util.spec_from_file_location(
31
+ "code_rules_enforcer",
32
+ _HOOK_DIRECTORY / "code_rules_enforcer.py",
33
+ )
34
+ assert _hook_spec is not None
35
+ assert _hook_spec.loader is not None
36
+ _hook_module = importlib.util.module_from_spec(_hook_spec)
37
+ _hook_spec.loader.exec_module(_hook_module)
38
+
39
+ VOID_ADVISORY_CHECK_FUNCTION_NAMES: frozenset[str] = frozenset(
40
+ {
41
+ "check_duplicated_format_patterns",
42
+ "check_incomplete_mocks",
43
+ }
44
+ )
45
+
46
+ KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW: frozenset[str] = frozenset(
47
+ {
48
+ "check_boolean_naming",
49
+ "check_collection_prefix",
50
+ "check_comment_changes",
51
+ "check_comments_javascript",
52
+ "check_comments_python",
53
+ "check_constant_equality_tests",
54
+ "check_constants_outside_config",
55
+ "check_constants_outside_config_advisory",
56
+ "check_e2e_test_naming",
57
+ "check_existence_check_tests",
58
+ "check_file_global_constants_use_count",
59
+ "check_fstring_structural_literals",
60
+ "check_imports_at_top",
61
+ "check_inline_literal_collections",
62
+ "check_library_print",
63
+ "check_logging_fstrings",
64
+ "check_loop_variable_naming",
65
+ "check_magic_values",
66
+ "check_parameter_annotations",
67
+ "check_return_annotations",
68
+ "check_skip_decorators_in_tests",
69
+ "check_string_literal_magic",
70
+ "check_type_escape_hatches",
71
+ "check_unused_optional_parameters",
72
+ "check_windows_api_none",
73
+ }
74
+ )
75
+
76
+ CAP_TOKEN_FRAGMENTS: tuple[str, ...] = ("MAX_", "islice")
77
+
78
+
79
+ def _all_check_function_names() -> list[str]:
80
+ return [
81
+ each_attribute_name
82
+ for each_attribute_name in dir(_hook_module)
83
+ if each_attribute_name.startswith("check_")
84
+ and callable(getattr(_hook_module, each_attribute_name))
85
+ ]
86
+
87
+
88
+ def _function_source_contains_cap(function_name: str) -> bool:
89
+ function_object = getattr(_hook_module, function_name)
90
+ function_source = inspect.getsource(function_object)
91
+ return any(
92
+ each_fragment in function_source for each_fragment in CAP_TOKEN_FRAGMENTS
93
+ )
94
+
95
+
96
+ def test_every_check_function_either_caps_or_is_explicitly_pending() -> None:
97
+ all_check_names = set(_all_check_function_names())
98
+ capped_check_names = {
99
+ each_name
100
+ for each_name in all_check_names
101
+ if _function_source_contains_cap(each_name)
102
+ }
103
+ uncapped_check_names = all_check_names - capped_check_names
104
+ unexpected_uncapped = (
105
+ uncapped_check_names
106
+ - KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW
107
+ - VOID_ADVISORY_CHECK_FUNCTION_NAMES
108
+ )
109
+ assert unexpected_uncapped == set(), (
110
+ f"New check_* functions added without a cap and not on the pending-review list: "
111
+ f"{sorted(unexpected_uncapped)}. Either add a MAX_* cap or islice-bounded loop in "
112
+ f"source, or explicitly add the function to KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW with "
113
+ f"a reason in the test header docstring, or list it in VOID_ADVISORY_CHECK_FUNCTION_NAMES "
114
+ f"when it is annotated -> None and emits only stderr guidance."
115
+ )
116
+
117
+
118
+ def test_pending_review_set_does_not_grow_silently() -> None:
119
+ all_check_names = set(_all_check_function_names())
120
+ stale_pending = KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW - all_check_names
121
+ assert stale_pending == set(), (
122
+ f"KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW lists functions that no longer exist: "
123
+ f"{sorted(stale_pending)}. Either restore the function or remove it from the list."
124
+ )
125
+
126
+
127
+ def test_pending_review_set_excludes_functions_that_already_match_cap_heuristic() -> None:
128
+ all_check_names = set(_all_check_function_names())
129
+ pending_that_exist = KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW & all_check_names
130
+ capped_but_still_pending = {
131
+ each_name
132
+ for each_name in pending_that_exist
133
+ if _function_source_contains_cap(each_name)
134
+ }
135
+ assert capped_but_still_pending == set(), (
136
+ f"KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW must not list checks that already satisfy the "
137
+ f"cap heuristic (would hide a later cap removal): {sorted(capped_but_still_pending)}. "
138
+ f"Remove those names from the pending set after capping."
139
+ )
140
+
141
+
142
+ def test_void_advisory_checks_are_registered_and_disjoint() -> None:
143
+ all_check_names = set(_all_check_function_names())
144
+ assert VOID_ADVISORY_CHECK_FUNCTION_NAMES <= all_check_names, (
145
+ f"VOID_ADVISORY_CHECK_FUNCTION_NAMES references missing names: "
146
+ f"{sorted(VOID_ADVISORY_CHECK_FUNCTION_NAMES - all_check_names)}"
147
+ )
148
+ for each_name in VOID_ADVISORY_CHECK_FUNCTION_NAMES:
149
+ function_object = getattr(_hook_module, each_name)
150
+ return_annotation = inspect.signature(function_object).return_annotation
151
+ assert return_annotation is None, (
152
+ f"VOID_ADVISORY_CHECK_FUNCTION_NAMES must list only advisory-only checks annotated "
153
+ f"with -> None (stderr-only, never block). {each_name!r} has return annotation "
154
+ f"{return_annotation!r}."
155
+ )
156
+ overlap = VOID_ADVISORY_CHECK_FUNCTION_NAMES & KNOWN_UNCAPPED_CHECKS_PENDING_REVIEW
157
+ assert overlap == set(), (
158
+ f"Void-advisory checks must not also appear on the uncapped pending list: "
159
+ f"{sorted(overlap)}"
160
+ )
161
+
162
+
163
+ def test_already_capped_checks_stay_capped() -> None:
164
+ capped_baseline: frozenset[str] = frozenset(
165
+ {
166
+ "check_banned_identifiers",
167
+ }
168
+ )
169
+ for each_name in capped_baseline:
170
+ assert _function_source_contains_cap(each_name), (
171
+ f"{each_name} previously had a cap reference and now does not. "
172
+ f"Caps protect blocking output from spamming reviewers — restore it."
173
+ )
@@ -0,0 +1,328 @@
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
+ )
138
+
139
+
140
+ def test_should_flag_function_local_double_all_prefix() -> None:
141
+ source = (
142
+ "def grant() -> None:\n"
143
+ " all_all_permission_rules = []\n"
144
+ )
145
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
146
+ source, PRODUCTION_FILE_PATH
147
+ )
148
+ assert any("all_all_permission_rules" in each_issue for each_issue in issues), (
149
+ f"PR #289 finding: stuttering all_all_ must be flagged, got: {issues}"
150
+ )
151
+
152
+
153
+ def test_should_flag_module_level_double_all_uppercase() -> None:
154
+ source = "ALL_ALL_PROVIDERS = []\n"
155
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
156
+ source, PRODUCTION_FILE_PATH
157
+ )
158
+ assert any("ALL_ALL_PROVIDERS" in each_issue for each_issue in issues), (
159
+ f"Stuttering ALL_ALL_ at module scope must be flagged, got: {issues}"
160
+ )
161
+
162
+
163
+ def test_should_flag_triple_all_prefix_stuttering() -> None:
164
+ source = (
165
+ "def consume() -> None:\n"
166
+ " all_all_all_things = []\n"
167
+ )
168
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
169
+ source, PRODUCTION_FILE_PATH
170
+ )
171
+ assert any("all_all_all_things" in each_issue for each_issue in issues), (
172
+ f"Triple all_ stuttering must be flagged, got: {issues}"
173
+ )
174
+
175
+
176
+ def test_should_not_flag_single_all_prefix() -> None:
177
+ source = (
178
+ "def consume() -> None:\n"
179
+ " all_users = []\n"
180
+ )
181
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
182
+ source, PRODUCTION_FILE_PATH
183
+ )
184
+ assert issues == [], (
185
+ f"Single all_ prefix is correct usage, must not flag, got: {issues}"
186
+ )
187
+
188
+
189
+ def test_should_flag_stuttering_in_function_parameter() -> None:
190
+ source = (
191
+ "def consume(all_all_users: list) -> None:\n"
192
+ " return None\n"
193
+ )
194
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
195
+ source, PRODUCTION_FILE_PATH
196
+ )
197
+ assert any("all_all_users" in each_issue for each_issue in issues), (
198
+ f"Stuttering parameter name must be flagged, got: {issues}"
199
+ )
200
+
201
+
202
+ def test_should_skip_stuttering_check_in_test_files() -> None:
203
+ source = (
204
+ "def test_something() -> None:\n"
205
+ " all_all_results = []\n"
206
+ )
207
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
208
+ source, "packages/app/tests/test_foo.py"
209
+ )
210
+ assert issues == [], f"Test files exempt, got: {issues}"
211
+
212
+
213
+ def test_should_not_flag_all_all_when_used_as_substring() -> None:
214
+ source = (
215
+ "def consume() -> None:\n"
216
+ " install_all_users = []\n"
217
+ )
218
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
219
+ source, PRODUCTION_FILE_PATH
220
+ )
221
+ assert issues == [], (
222
+ f"'all_all_' as substring inside install_all_users must not flag, got: {issues}"
223
+ )
224
+
225
+
226
+ def test_should_flag_underscore_prefixed_uppercase_double_all() -> None:
227
+ source = "_ALL_ALL_PROVIDERS = []\n"
228
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
229
+ source, PRODUCTION_FILE_PATH
230
+ )
231
+ assert any("_ALL_ALL_PROVIDERS" in each_issue for each_issue in issues), (
232
+ f"Stuttering _ALL_ALL_ private constant must be flagged (regex symmetry), got: {issues}"
233
+ )
234
+
235
+
236
+ def test_should_flag_underscore_prefixed_lowercase_double_all() -> None:
237
+ source = (
238
+ "def grant() -> None:\n"
239
+ " _all_all_permission_rules = []\n"
240
+ )
241
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
242
+ source, PRODUCTION_FILE_PATH
243
+ )
244
+ assert any("_all_all_permission_rules" in each_issue for each_issue in issues), (
245
+ f"Stuttering _all_all_ private local must be flagged, got: {issues}"
246
+ )
247
+
248
+
249
+ def test_should_flag_stuttering_function_name() -> None:
250
+ source = "def all_all_process() -> None:\n return None\n"
251
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
252
+ source, PRODUCTION_FILE_PATH
253
+ )
254
+ assert any("all_all_process" in each_issue for each_issue in issues), (
255
+ f"Stuttering function name must be flagged, got: {issues}"
256
+ )
257
+
258
+
259
+ def test_should_flag_stuttering_async_function_name() -> None:
260
+ source = "async def all_all_fetch() -> None:\n return None\n"
261
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
262
+ source, PRODUCTION_FILE_PATH
263
+ )
264
+ assert any("all_all_fetch" in each_issue for each_issue in issues), (
265
+ f"Stuttering async function name must be flagged, got: {issues}"
266
+ )
267
+
268
+
269
+ def test_should_flag_stuttering_walrus_target() -> None:
270
+ source = (
271
+ "def grant() -> None:\n"
272
+ " if (all_all_result := compute()):\n"
273
+ " return None\n"
274
+ "def compute() -> int:\n"
275
+ " return 0\n"
276
+ )
277
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
278
+ source, PRODUCTION_FILE_PATH
279
+ )
280
+ assert any("all_all_result" in each_issue for each_issue in issues), (
281
+ f"Stuttering walrus target must be flagged, got: {issues}"
282
+ )
283
+
284
+
285
+ def test_should_flag_stuttering_comprehension_target() -> None:
286
+ source = (
287
+ "def grant() -> list[int]:\n"
288
+ " return [all_all_item for all_all_item in range(10)]\n"
289
+ )
290
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
291
+ source, PRODUCTION_FILE_PATH
292
+ )
293
+ assert any("all_all_item" in each_issue for each_issue in issues), (
294
+ f"Stuttering comprehension target must be flagged, got: {issues}"
295
+ )
296
+
297
+
298
+ def test_should_flag_stuttering_import_as_alias() -> None:
299
+ source = "import collections as all_all_collections\n"
300
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
301
+ source, PRODUCTION_FILE_PATH
302
+ )
303
+ assert any("all_all_collections" in each_issue for each_issue in issues), (
304
+ f"Stuttering import-as alias must be flagged, got: {issues}"
305
+ )
306
+
307
+
308
+ def test_should_flag_stuttering_from_import_as_alias() -> None:
309
+ source = "from collections import OrderedDict as all_all_ordered\n"
310
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
311
+ source, PRODUCTION_FILE_PATH
312
+ )
313
+ assert any("all_all_ordered" in each_issue for each_issue in issues), (
314
+ f"Stuttering from-import as alias must be flagged, got: {issues}"
315
+ )
316
+
317
+
318
+ def test_should_flag_stuttering_class_name() -> None:
319
+ source = (
320
+ "class all_all_models:\n"
321
+ " value = 1\n"
322
+ )
323
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
324
+ source, PRODUCTION_FILE_PATH
325
+ )
326
+ assert any("all_all_models" in each_issue for each_issue in issues), (
327
+ f"Stuttering class name must be flagged, got: {issues}"
328
+ )
@@ -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,43 @@ 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:
113
+ def test_should_flag_literal_on_left_with_constant_on_right() -> None:
114
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"
115
+ 'CACHE_DIR = "cache"\n'
120
116
  "\n"
121
- "def test_gamma() -> None:\n assert GAMMA == 3\n"
117
+ "def test_cache_dir_right() -> None:\n"
118
+ ' assert "cache" == CACHE_DIR\n'
119
+ )
120
+ issues = code_rules_enforcer.check_constant_equality_tests(source, TEST_FILE_PATH)
121
+ assert any("constant" in issue.lower() for issue in issues), (
122
+ f"Expected 'cache' == CACHE_DIR flagged equally — equality is commutative, got: {issues}"
123
+ )
124
+
125
+
126
+ def test_should_flag_numeric_literal_on_left_with_constant_on_right() -> None:
127
+ source = (
128
+ "MAX_SIZE = 100\n"
122
129
  "\n"
123
- "def test_delta() -> None:\n assert DELTA == 4\n"
130
+ "def test_max_size_right() -> None:\n"
131
+ " assert 100 == MAX_SIZE\n"
132
+ )
133
+ issues = code_rules_enforcer.check_constant_equality_tests(source, TEST_FILE_PATH)
134
+ assert any("constant" in issue.lower() for issue in issues), (
135
+ f"Expected 100 == MAX_SIZE flagged equally, got: {issues}"
136
+ )
137
+
138
+
139
+ def test_should_not_flag_two_upper_snake_constants_compared() -> None:
140
+ source = (
141
+ "EXPECTED = 5\n"
142
+ "ACTUAL = 5\n"
124
143
  "\n"
125
- "def test_epsilon() -> None:\n assert EPSILON == 5\n"
144
+ "def test_two_constants() -> None:\n"
145
+ " assert EXPECTED == ACTUAL\n"
126
146
  )
127
147
  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}"
148
+ assert issues == [], (
149
+ f"Two-constant comparison is not constant-vs-literal, must not flag, got: {issues}"
130
150
  )
151
+
152
+
@@ -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"