claude-dev-env 1.35.0 → 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 (113) hide show
  1. package/agents/clean-coder.md +109 -1
  2. package/bin/install.mjs +28 -8
  3. package/bin/install.test.mjs +9 -1
  4. package/docs/CODE_RULES.md +3 -0
  5. package/docs/agents-md-alignment-plan.md +123 -0
  6. package/hooks/blocking/code_rules_enforcer.py +451 -39
  7. package/hooks/blocking/es_exe_path_rewriter.py +10 -4
  8. package/hooks/blocking/test_code_rules_enforcer.py +182 -0
  9. package/hooks/blocking/test_code_rules_enforcer_banned_identifier.py +106 -0
  10. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +173 -0
  11. package/hooks/blocking/test_code_rules_enforcer_collection_prefix.py +191 -0
  12. package/hooks/blocking/test_code_rules_enforcer_constant_equality.py +40 -0
  13. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +291 -0
  14. package/hooks/blocking/test_code_rules_enforcer_loop_variable_naming.py +87 -3
  15. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +49 -0
  16. package/hooks/blocking/test_code_rules_enforcer_sys_path_insert.py +157 -0
  17. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +244 -0
  18. package/hooks/blocking/test_es_exe_path_rewriter.py +81 -3
  19. package/hooks/blocking/test_windows_rmtree_blocker.py +120 -8
  20. package/hooks/blocking/windows_rmtree_blocker.py +23 -6
  21. package/hooks/config/banned_identifiers_constants.py +24 -0
  22. package/hooks/config/hardcoded_user_path_constants.py +12 -0
  23. package/hooks/config/hook_log_extractor_constants.py +1 -1
  24. package/hooks/config/pre_tool_use_stdin.py +48 -0
  25. package/hooks/config/setup_project_paths_constants.py +4 -0
  26. package/hooks/config/stuttering_check_config.py +14 -0
  27. package/hooks/config/stuttering_import_binding_constants.py +11 -0
  28. package/hooks/config/sys_path_insert_constants.py +4 -0
  29. package/hooks/config/test_banned_identifiers_constants.py +48 -0
  30. package/hooks/config/test_hardcoded_user_path_constants.py +78 -0
  31. package/hooks/config/test_hook_log_extractor_constants.py +3 -3
  32. package/hooks/config/test_pre_tool_use_stdin.py +80 -0
  33. package/hooks/config/unused_module_import_constants.py +7 -0
  34. package/hooks/config/windows_rmtree_blocker_constants.py +3 -0
  35. package/hooks/diagnostic/hook_log_stop_wrapper.py +7 -4
  36. package/hooks/git-hooks/config.py +3 -3
  37. package/hooks/git-hooks/test_gate_utils.py +10 -10
  38. package/hooks/mypy.ini +2 -0
  39. package/package.json +1 -1
  40. package/rules/gh-paginate.md +125 -0
  41. package/skills/bugteam/CONSTRAINTS.md +12 -6
  42. package/skills/bugteam/SKILL.md +77 -91
  43. package/skills/bugteam/SKILL_EVALS.md +25 -23
  44. package/skills/bugteam/reference/README.md +2 -0
  45. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  46. package/skills/bugteam/reference/teardown-publish-permissions.md +1 -1
  47. package/skills/bugteam/reference/workflow-path-a-orchestrated-teams.md +113 -0
  48. package/skills/bugteam/reference/workflow-path-b-task-harness.md +48 -0
  49. package/skills/bugteam/test_skill_additions.py +13 -4
  50. package/skills/bugteam/test_team_lifecycle.py +94 -0
  51. package/skills/findbugs/SKILL.md +3 -3
  52. package/skills/fixbugs/SKILL.md +4 -4
  53. package/skills/monitor-open-prs/SKILL.md +32 -2
  54. package/skills/monitor-open-prs/test_team_lifecycle.py +46 -0
  55. package/skills/pr-converge/SKILL.md +562 -97
  56. package/skills/pr-converge/scripts/README.md +145 -0
  57. package/skills/pr-converge/scripts/caller-window-pid.ps1 +86 -0
  58. package/skills/pr-converge/scripts/check_pr_mergeability.py +79 -0
  59. package/skills/pr-converge/scripts/config/pr_converge_constants.py +65 -0
  60. package/skills/pr-converge/scripts/config/test_pr_converge_constants.py +176 -0
  61. package/skills/pr-converge/scripts/cursor-agents-continue-caller.cmd +9 -0
  62. package/skills/pr-converge/scripts/cursor-agents-continue-stop-others.ps1 +16 -0
  63. package/skills/pr-converge/scripts/cursor-agents-continue.ahk +172 -0
  64. package/skills/pr-converge/scripts/cursor-agents-continue.cmd +2 -0
  65. package/skills/pr-converge/scripts/evict_cached_config_modules.py +20 -0
  66. package/skills/pr-converge/scripts/fetch_bugbot_inline_comments.py +110 -0
  67. package/skills/pr-converge/scripts/fetch_bugbot_reviews.py +103 -0
  68. package/skills/pr-converge/scripts/fetch_copilot_inline_comments.py +112 -0
  69. package/skills/pr-converge/scripts/fetch_copilot_reviews.py +121 -0
  70. package/skills/pr-converge/scripts/mark_pr_ready.py +54 -0
  71. package/skills/pr-converge/scripts/open_followup_copilot_pr.py +136 -0
  72. package/skills/pr-converge/scripts/post-bugbot-run.helpers.ps1 +49 -0
  73. package/skills/pr-converge/scripts/post-bugbot-run.ps1 +33 -0
  74. package/skills/pr-converge/scripts/reply_to_inline_comment.py +84 -0
  75. package/skills/pr-converge/scripts/request_copilot_review.py +71 -0
  76. package/skills/pr-converge/scripts/resolve_pr_head.py +58 -0
  77. package/skills/pr-converge/scripts/review_field_helpers.py +43 -0
  78. package/skills/pr-converge/scripts/test_check_pr_mergeability.py +126 -0
  79. package/skills/pr-converge/scripts/test_evict_cached_config_modules.py +22 -0
  80. package/skills/pr-converge/scripts/test_fetch_bugbot_inline_comments.py +342 -0
  81. package/skills/pr-converge/scripts/test_fetch_bugbot_reviews.py +220 -0
  82. package/skills/pr-converge/scripts/test_fetch_copilot_inline_comments.py +372 -0
  83. package/skills/pr-converge/scripts/test_fetch_copilot_reviews.py +280 -0
  84. package/skills/pr-converge/scripts/test_mark_pr_ready.py +69 -0
  85. package/skills/pr-converge/scripts/test_open_followup_copilot_pr.py +236 -0
  86. package/skills/pr-converge/scripts/test_post_bugbot_run.py +195 -0
  87. package/skills/pr-converge/scripts/test_reply_to_inline_comment.py +159 -0
  88. package/skills/pr-converge/scripts/test_request_copilot_review.py +101 -0
  89. package/skills/pr-converge/scripts/test_resolve_pr_head.py +79 -0
  90. package/skills/pr-converge/scripts/test_review_field_helpers.py +80 -0
  91. package/skills/pr-converge/scripts/test_trigger_bugbot.py +139 -0
  92. package/skills/pr-converge/scripts/test_view_pr_context.py +111 -0
  93. package/skills/pr-converge/scripts/trigger_bugbot.py +77 -0
  94. package/skills/pr-converge/scripts/view_pr_context.py +47 -0
  95. package/skills/pr-converge/test_team_lifecycle.py +47 -0
  96. package/skills/pr-converge/workflows/ahk-auto-continue-loop.md +108 -0
  97. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +37 -0
  98. package/skills/qbug/SKILL.md +4 -4
  99. package/skills/qbug/test_qbug_skill_post_fix_audit.py +2 -2
  100. package/skills/resume-review/SKILL.md +261 -0
  101. package/skills/bugteam/scripts/README.md +0 -58
  102. package/skills/bugteam/scripts/_claude_permissions_common.py +0 -219
  103. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +0 -633
  104. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +0 -260
  105. package/skills/bugteam/scripts/bugteam_preflight.py +0 -201
  106. package/skills/bugteam/scripts/config/bugteam_fix_hookspath_constants.py +0 -17
  107. package/skills/bugteam/scripts/grant_project_claude_permissions.py +0 -109
  108. package/skills/bugteam/scripts/revoke_project_claude_permissions.py +0 -135
  109. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +0 -271
  110. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +0 -267
  111. package/skills/bugteam/scripts/test_bugteam_preflight.py +0 -189
  112. package/skills/bugteam/scripts/test_claude_permissions_common.py +0 -44
  113. /package/skills/{bugteam → pr-converge}/scripts/config/__init__.py +0 -0
@@ -33,14 +33,82 @@ def _load_enforcer_module() -> ModuleType:
33
33
  code_rules_enforcer = _load_enforcer_module()
34
34
 
35
35
  _BLOCKING_DIR = Path(__file__).resolve().parent
36
+ _HOOKS_TREE_DIR = _BLOCKING_DIR.parent
36
37
  if str(_BLOCKING_DIR) not in sys.path:
37
38
  sys.path.insert(0, str(_BLOCKING_DIR))
39
+ if str(_HOOKS_TREE_DIR) not in sys.path:
40
+ sys.path.insert(0, str(_HOOKS_TREE_DIR))
38
41
 
39
42
  from code_rules_path_utils import is_config_file as path_utils_is_config_file # noqa: E402
43
+ from config.banned_identifiers_constants import ( # noqa: E402
44
+ ALL_BANNED_IDENTIFIERS as config_all_banned_identifiers,
45
+ BANNED_IDENTIFIER_MESSAGE_SUFFIX as config_banned_identifier_message_suffix,
46
+ BANNED_IDENTIFIER_SKIP_ADVISORY as config_banned_identifier_skip_advisory,
47
+ MAX_BANNED_IDENTIFIER_ISSUES as config_max_banned_identifier_issues,
48
+ )
49
+ from config.hardcoded_user_path_constants import ( # noqa: E402
50
+ HARDCODED_USER_PATH_GUIDANCE as config_hardcoded_user_path_guidance,
51
+ HARDCODED_USER_PATH_PATTERN as config_hardcoded_user_path_pattern,
52
+ MAX_HARDCODED_USER_PATH_ISSUES as config_max_hardcoded_user_path_issues,
53
+ )
54
+ from config.stuttering_check_config import ( # noqa: E402
55
+ MAX_STUTTERING_PREFIX_ISSUES as config_max_stuttering_prefix_issues,
56
+ STUTTERING_ALL_PREFIX_PATTERN as config_stuttering_all_prefix_pattern,
57
+ )
40
58
 
41
59
  PRODUCTION_FILE_PATH = "packages/claude-dev-env/hooks/blocking/example_production.py"
42
60
 
43
61
 
62
+ def test_should_expose_all_banned_identifiers_from_config() -> None:
63
+ expected_banned_identifiers = frozenset({
64
+ "result", "data", "output", "response", "value", "item", "temp",
65
+ "argv", "args", "kwargs", "argc",
66
+ })
67
+ actual_banned_identifiers = getattr(
68
+ code_rules_enforcer, "ALL_BANNED_IDENTIFIERS", None
69
+ )
70
+ assert actual_banned_identifiers is not None, (
71
+ "Renamed constant ALL_BANNED_IDENTIFIERS must be importable from "
72
+ "config/banned_identifiers_constants.py and re-exposed on the "
73
+ f"enforcer module, got: {actual_banned_identifiers!r}"
74
+ )
75
+ assert expected_banned_identifiers <= actual_banned_identifiers, (
76
+ "ALL_BANNED_IDENTIFIERS must contain every expected banned identifier; "
77
+ f"missing: {expected_banned_identifiers - actual_banned_identifiers!r}"
78
+ )
79
+
80
+
81
+ def test_should_source_banned_identifier_companion_constants_from_config() -> None:
82
+ assert (
83
+ code_rules_enforcer.MAX_BANNED_IDENTIFIER_ISSUES
84
+ is config_max_banned_identifier_issues
85
+ )
86
+ assert (
87
+ code_rules_enforcer.BANNED_IDENTIFIER_MESSAGE_SUFFIX
88
+ is config_banned_identifier_message_suffix
89
+ )
90
+ assert (
91
+ code_rules_enforcer.BANNED_IDENTIFIER_SKIP_ADVISORY
92
+ is config_banned_identifier_skip_advisory
93
+ )
94
+
95
+
96
+ def test_should_reexport_hardcoded_user_path_pattern_from_config() -> None:
97
+ assert code_rules_enforcer.HARDCODED_USER_PATH_PATTERN is config_hardcoded_user_path_pattern
98
+
99
+
100
+ def test_should_reexport_max_hardcoded_user_path_issues_from_config() -> None:
101
+ assert code_rules_enforcer.MAX_HARDCODED_USER_PATH_ISSUES == config_max_hardcoded_user_path_issues
102
+
103
+
104
+ def test_should_reexport_hardcoded_user_path_guidance_from_config() -> None:
105
+ assert code_rules_enforcer.HARDCODED_USER_PATH_GUIDANCE == config_hardcoded_user_path_guidance
106
+
107
+
108
+ def test_should_reexport_all_banned_identifiers_from_config() -> None:
109
+ assert code_rules_enforcer.ALL_BANNED_IDENTIFIERS is config_all_banned_identifiers
110
+
111
+
44
112
  def test_should_flag_constant_used_only_in_class_level_decorator() -> None:
45
113
  source = (
46
114
  "TIMEOUT = 5\n"
@@ -1010,3 +1078,117 @@ def test_check_constants_outside_config_reports_more_than_three_constants() -> N
1010
1078
  assert len(issues) == expected_constant_count, (
1011
1079
  f"Expected all {expected_constant_count} constants reported, got {len(issues)}: {issues}"
1012
1080
  )
1081
+
1082
+
1083
+ def test_stuttering_collection_prefix_flags_function_name_loop1_1() -> None:
1084
+ source = "def all_all_process() -> None:\n return None\n"
1085
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
1086
+ source, "packages/app/services/foo.py"
1087
+ )
1088
+ assert any("all_all_process" in each_issue for each_issue in issues), (
1089
+ f"loop1-1: stuttering function name must be flagged, got: {issues}"
1090
+ )
1091
+
1092
+
1093
+ def test_stuttering_collection_prefix_flags_with_as_binding_loop3_1() -> None:
1094
+ source = "def f() -> None:\n with open('x') as all_all_context:\n pass\n"
1095
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
1096
+ source, "packages/app/services/foo.py"
1097
+ )
1098
+ assert any("all_all_context" in each_issue for each_issue in issues), (
1099
+ f"loop3-1: stuttering with-as binding must be flagged, got: {issues}"
1100
+ )
1101
+
1102
+
1103
+ def test_stuttering_collection_prefix_flags_except_as_binding_loop3_1() -> None:
1104
+ source = (
1105
+ "def f() -> None:\n"
1106
+ " try:\n"
1107
+ " pass\n"
1108
+ " except Exception as all_all_error:\n"
1109
+ " pass\n"
1110
+ )
1111
+ issues = code_rules_enforcer.check_stuttering_collection_prefix(
1112
+ source, "packages/app/services/foo.py"
1113
+ )
1114
+ assert any("all_all_error" in each_issue for each_issue in issues), (
1115
+ f"loop3-1: stuttering except-as binding must be flagged, got: {issues}"
1116
+ )
1117
+
1118
+
1119
+ def test_stuttering_constants_live_under_config_subpackage() -> None:
1120
+ """Stuttering-prefix constants must be sourced from the hooks-tree config package.
1121
+
1122
+ Per CODE_RULES, module-level UPPER_SNAKE constants must live under a
1123
+ directory segment named ``config``. This test pins the move so the
1124
+ constants cannot regress to inline definition at the enforcer module's
1125
+ top level. The enforcer's own bootstrap inserts the hooks tree onto
1126
+ ``sys.path`` so ``config.stuttering_check_config`` resolves at runtime.
1127
+ """
1128
+ assert (
1129
+ code_rules_enforcer.STUTTERING_ALL_PREFIX_PATTERN
1130
+ is config_stuttering_all_prefix_pattern
1131
+ ), "Enforcer must reuse the hooks-tree config STUTTERING_ALL_PREFIX_PATTERN object"
1132
+ assert (
1133
+ code_rules_enforcer.MAX_STUTTERING_PREFIX_ISSUES
1134
+ == config_max_stuttering_prefix_issues
1135
+ ), "Enforcer must reuse the hooks-tree config MAX_STUTTERING_PREFIX_ISSUES value"
1136
+
1137
+
1138
+ SYS_PATH_INSERT_PRODUCTION_FILE_PATH = "packages/app/services/loader.py"
1139
+ SYS_PATH_INSERT_HOOK_INFRASTRUCTURE_FILE_PATH = "/repo/.claude/hooks/blocking/some_hook.py"
1140
+
1141
+
1142
+ def test_sys_path_insert_should_flag_mismatched_guard_path() -> None:
1143
+ source = (
1144
+ "import sys\n"
1145
+ 'if "wrong_path" not in sys.path:\n'
1146
+ ' sys.path.insert(0, "actual_path")\n'
1147
+ )
1148
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
1149
+ source, SYS_PATH_INSERT_PRODUCTION_FILE_PATH
1150
+ )
1151
+ assert any("sys.path.insert" in each_issue for each_issue in issues), (
1152
+ "Guard testing a different value than what is inserted must be flagged, "
1153
+ f"got: {issues}"
1154
+ )
1155
+
1156
+
1157
+ def test_sys_path_insert_should_not_flag_matching_guard_path() -> None:
1158
+ source = (
1159
+ "import sys\n"
1160
+ 'if "correct_path" not in sys.path:\n'
1161
+ ' sys.path.insert(0, "correct_path")\n'
1162
+ )
1163
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
1164
+ source, SYS_PATH_INSERT_PRODUCTION_FILE_PATH
1165
+ )
1166
+ assert issues == [], (
1167
+ f"Guard testing the same value that is inserted must not be flagged, got: {issues}"
1168
+ )
1169
+
1170
+
1171
+ def test_sys_path_insert_should_not_flag_guarded_insert_in_class_body() -> None:
1172
+ source = (
1173
+ "import sys\n"
1174
+ "class Configurator:\n"
1175
+ " target = '/some/path'\n"
1176
+ " if target not in sys.path:\n"
1177
+ " sys.path.insert(0, target)\n"
1178
+ )
1179
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
1180
+ source, SYS_PATH_INSERT_PRODUCTION_FILE_PATH
1181
+ )
1182
+ assert issues == [], (
1183
+ f"Guarded sys.path.insert directly in a class body must not be flagged, got: {issues}"
1184
+ )
1185
+
1186
+
1187
+ def test_sys_path_insert_should_skip_hook_infrastructure_files() -> None:
1188
+ source = "import sys\nsys.path.insert(0, '/some/path')\n"
1189
+ issues = code_rules_enforcer.check_sys_path_insert_deduplication_guard(
1190
+ source, SYS_PATH_INSERT_HOOK_INFRASTRUCTURE_FILE_PATH
1191
+ )
1192
+ assert issues == [], (
1193
+ f"Hook infrastructure files are exempt from this rule, got: {issues}"
1194
+ )
@@ -229,3 +229,109 @@ def test_should_emit_stderr_advisory_on_syntax_error(
229
229
  captured = capsys.readouterr() # type: ignore[attr-defined]
230
230
  assert "banned-identifier check skipped" in captured.err
231
231
  assert PRODUCTION_FILE_PATH in captured.err
232
+
233
+
234
+ def test_should_flag_argv_assignment() -> None:
235
+ content = "def parse_command():\n argv = collect()\n return argv\n"
236
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
237
+ assert any("'argv'" in each_issue for each_issue in issues), (
238
+ f"Expected 'argv' flagged — use arguments_list, got: {issues}"
239
+ )
240
+
241
+
242
+ def test_should_flag_args_assignment() -> None:
243
+ content = "def parse_command():\n args = collect()\n return args\n"
244
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
245
+ assert any("'args'" in each_issue for each_issue in issues), (
246
+ f"Expected 'args' flagged — use arguments, got: {issues}"
247
+ )
248
+
249
+
250
+ def test_should_not_flag_args_assigned_parse_args_call() -> None:
251
+ content = (
252
+ "def main():\n"
253
+ " parser = build_parser()\n"
254
+ " args = parser.parse_args()\n"
255
+ " return args\n"
256
+ )
257
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
258
+ assert issues == [], (
259
+ "args = parser.parse_args() is established argparse idiom; must not flag, "
260
+ f"got: {issues}"
261
+ )
262
+
263
+
264
+ def test_should_not_flag_args_annotated_parse_args_call() -> None:
265
+ content = (
266
+ "def main():\n"
267
+ " parser = build_parser()\n"
268
+ " args: object = parser.parse_args()\n"
269
+ " return args\n"
270
+ )
271
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
272
+ assert issues == [], f"Annotated parse_args binding must not flag, got: {issues}"
273
+
274
+
275
+ def test_should_not_flag_args_walrus_parse_args_call() -> None:
276
+ content = (
277
+ "def main():\n"
278
+ " parser = build_parser()\n"
279
+ " if (args := parser.parse_args()):\n"
280
+ " return args\n"
281
+ " return None\n"
282
+ )
283
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
284
+ assert issues == [], f"Walrus parse_args binding must not flag, got: {issues}"
285
+
286
+
287
+ def test_should_flag_args_assigned_parse_args_method_reference() -> None:
288
+ content = (
289
+ "def main():\n"
290
+ " parser = build_parser()\n"
291
+ " args = parser.parse_args\n"
292
+ " return args\n"
293
+ )
294
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
295
+ assert any("'args'" in each_issue for each_issue in issues), (
296
+ "Method reference (no call) is not the namespace idiom; must flag, "
297
+ f"got: {issues}"
298
+ )
299
+
300
+
301
+ def test_should_flag_kwargs_assignment() -> None:
302
+ content = "def parse_command():\n kwargs = collect()\n return kwargs\n"
303
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
304
+ assert any("'kwargs'" in each_issue for each_issue in issues), (
305
+ f"Expected 'kwargs' flagged — use keyword_arguments, got: {issues}"
306
+ )
307
+
308
+
309
+ def test_should_flag_argc_assignment() -> None:
310
+ content = "def parse_command():\n argc = count()\n return argc\n"
311
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
312
+ assert any("'argc'" in each_issue for each_issue in issues), (
313
+ f"Expected 'argc' flagged — use argument_count, got: {issues}"
314
+ )
315
+
316
+
317
+ def test_should_not_flag_args_as_function_parameter() -> None:
318
+ content = (
319
+ "def passthrough(*args, **kwargs):\n"
320
+ " return args, kwargs\n"
321
+ )
322
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
323
+ assert issues == [], (
324
+ f"*args/**kwargs parameters are Python convention, must not flag, got: {issues}"
325
+ )
326
+
327
+
328
+ def test_should_not_flag_argv_substring_in_local_name() -> None:
329
+ content = (
330
+ "def parse_command():\n"
331
+ " parsed_argv_entries = []\n"
332
+ " return parsed_argv_entries\n"
333
+ )
334
+ issues = check_banned_identifiers(content, PRODUCTION_FILE_PATH)
335
+ assert issues == [], (
336
+ f"Substring 'argv' inside parsed_argv_entries must not flag, got: {issues}"
337
+ )
@@ -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
+ )
@@ -135,3 +135,194 @@ def test_should_flag_module_level_qualified_typing_optional_constant() -> None:
135
135
  assert any("RAW_NUMBERS" in each_issue for each_issue in issues), (
136
136
  f"Expected typing.Optional[set[int]] module-level constant flagged, got: {issues}"
137
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
+ )