claude-dev-env 1.71.0 → 1.73.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 (68) hide show
  1. package/CLAUDE.md +8 -0
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +5 -3
  3. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +39 -0
  4. package/agents/clean-coder.md +1 -0
  5. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +2 -2
  6. package/bin/install.mjs +73 -5
  7. package/bin/install.test.mjs +360 -4
  8. package/docs/CODE_RULES.md +1 -1
  9. package/hooks/blocking/CLAUDE.md +3 -1
  10. package/hooks/blocking/claude_md_orphan_file_blocker.py +5 -6
  11. package/hooks/blocking/code_rules_dead_config_field.py +69 -56
  12. package/hooks/blocking/code_rules_docstrings.py +676 -0
  13. package/hooks/blocking/code_rules_enforcer.py +26 -0
  14. package/hooks/blocking/code_rules_shared.py +19 -0
  15. package/hooks/blocking/code_rules_test_assertions.py +152 -1
  16. package/hooks/blocking/code_rules_type_escape.py +447 -2
  17. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +420 -0
  18. package/hooks/blocking/md_to_html_blocker.py +7 -8
  19. package/hooks/blocking/open_questions_in_plans_blocker.py +5 -6
  20. package/hooks/blocking/plain_language_blocker.py +51 -16
  21. package/hooks/blocking/pr_converge_bugteam_enforcer.py +5 -5
  22. package/hooks/blocking/pre_tool_use_dispatcher.py +545 -0
  23. package/hooks/blocking/pytest_testpaths_orphan_blocker.py +358 -0
  24. package/hooks/blocking/state_description_blocker.py +75 -36
  25. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +81 -0
  26. package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +93 -0
  27. package/hooks/blocking/test_code_rules_enforcer_docstring_no_consumer.py +93 -0
  28. package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +262 -0
  29. package/hooks/blocking/test_code_rules_enforcer_docstring_undefined_constant.py +253 -0
  30. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +279 -0
  31. package/hooks/blocking/test_code_rules_enforcer_object_parameter.py +499 -0
  32. package/hooks/blocking/test_code_rules_enforcer_stale_test_name.py +103 -0
  33. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +456 -0
  34. package/hooks/blocking/test_pre_tool_use_dispatcher.py +816 -0
  35. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +341 -0
  36. package/hooks/blocking/test_pytest_testpaths_orphan_blocker.py +247 -0
  37. package/hooks/blocking/test_shared_stdin_adoption.py +166 -0
  38. package/hooks/blocking/verdict_directory_write_blocker.py +12 -7
  39. package/hooks/hooks.json +9 -79
  40. package/hooks/hooks_constants/CLAUDE.md +3 -1
  41. package/hooks/hooks_constants/blocking_check_limits.py +75 -0
  42. package/hooks/hooks_constants/code_rules_enforcer_constants.py +6 -0
  43. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +45 -0
  44. package/hooks/hooks_constants/dead_config_field_constants.py +5 -5
  45. package/hooks/hooks_constants/mypy_validator_cache_constants.py +36 -0
  46. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +69 -0
  47. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +135 -0
  48. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +1 -1
  49. package/hooks/hooks_constants/pytest_testpaths_orphan_blocker_constants.py +79 -0
  50. package/hooks/validation/mypy_validator.py +215 -17
  51. package/hooks/validation/post_tool_use_dispatcher.py +344 -0
  52. package/hooks/validation/test_mypy_validator.py +184 -1
  53. package/hooks/validation/test_post_tool_use_dispatcher.py +610 -0
  54. package/hooks/workflow/test_auto_formatter.py +10 -9
  55. package/package.json +1 -1
  56. package/rules/docstring-prose-matches-implementation.md +3 -2
  57. package/scripts/CLAUDE.md +1 -0
  58. package/scripts/Show-Asset.ps1 +106 -0
  59. package/skills/autoconverge/SKILL.md +123 -3
  60. package/skills/autoconverge/reference/convergence.md +41 -1
  61. package/skills/autoconverge/workflow/converge.contract.test.mjs +90 -0
  62. package/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +98 -0
  63. package/skills/autoconverge/workflow/converge.mjs +203 -8
  64. package/skills/autoconverge/workflow/converge.path-aware.test.mjs +47 -0
  65. package/skills/autoconverge/workflow/converge_multi.mjs +161 -0
  66. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +100 -0
  67. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +47 -3
  68. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -0
@@ -0,0 +1,279 @@
1
+ """Tests for the module-docstring check roster and docstring tuple-enumeration checks.
2
+
3
+ Both checks catch docstring-prose-vs-implementation drift in a check-registry
4
+ module — a hook module that exposes several public ``check_*`` functions and a
5
+ module-level tuple of literal attribute names. The drift the
6
+ ``code_rules_test_assertions.py`` module hit at PR #713 HEAD: a one-line module
7
+ docstring that names four of its five public checks, and a function docstring
8
+ that enumerates three plumbing attributes while the tuple it reads holds four.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import importlib.util
14
+ from pathlib import Path
15
+ from types import ModuleType
16
+
17
+
18
+ def _load_enforcer_module() -> ModuleType:
19
+ module_path = Path(__file__).parent / "code_rules_enforcer.py"
20
+ spec = importlib.util.spec_from_file_location("code_rules_enforcer", module_path)
21
+ assert spec is not None
22
+ assert spec.loader is not None
23
+ module = importlib.util.module_from_spec(spec)
24
+ spec.loader.exec_module(module)
25
+ return module
26
+
27
+
28
+ code_rules_enforcer = _load_enforcer_module()
29
+
30
+
31
+ def check_module_docstring_names_public_checks(content: str, file_path: str) -> list[str]:
32
+ return code_rules_enforcer.check_module_docstring_names_public_checks(content, file_path)
33
+
34
+
35
+ def check_docstring_tuple_enumeration_match(content: str, file_path: str) -> list[str]:
36
+ return code_rules_enforcer.check_docstring_tuple_enumeration_match(content, file_path)
37
+
38
+
39
+ def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
40
+ return code_rules_enforcer.validate_content(content, file_path, old_content)
41
+
42
+
43
+ HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/code_rules_test_assertions.py"
44
+ PRODUCTION_FILE_PATH = "/project/src/registry.py"
45
+ TEST_FILE_PATH = "/project/src/test_registry.py"
46
+
47
+
48
+ def _registry_module_omitting_a_check() -> str:
49
+ return (
50
+ '"""Skip-decorator, existence-only, and constant-equality test-quality checks."""\n'
51
+ "\n"
52
+ "def check_skip_decorators(content: str, file_path: str) -> list[str]:\n"
53
+ " return []\n"
54
+ "\n"
55
+ "def check_existence_check(content: str, file_path: str) -> list[str]:\n"
56
+ " return []\n"
57
+ "\n"
58
+ "def check_constant_equality(content: str, file_path: str) -> list[str]:\n"
59
+ " return []\n"
60
+ "\n"
61
+ "def check_behavior_named_mock(content: str, file_path: str) -> list[str]:\n"
62
+ " return []\n"
63
+ )
64
+
65
+
66
+ def test_should_flag_module_docstring_omitting_a_public_check() -> None:
67
+ issues = check_module_docstring_names_public_checks(
68
+ _registry_module_omitting_a_check(), HOOK_INFRASTRUCTURE_PATH
69
+ )
70
+ assert any("check_behavior_named_mock" in each for each in issues), (
71
+ f"Expected the omitted check to flag, got: {issues!r}"
72
+ )
73
+ assert len(issues) == 1
74
+
75
+
76
+ def test_should_not_flag_module_docstring_naming_every_public_check() -> None:
77
+ source = (
78
+ '"""Skip-decorator, existence-check, constant-equality, and behavior-named-mock checks."""\n'
79
+ "\n"
80
+ "def check_skip_decorators(content: str, file_path: str) -> list[str]:\n"
81
+ " return []\n"
82
+ "\n"
83
+ "def check_existence_check(content: str, file_path: str) -> list[str]:\n"
84
+ " return []\n"
85
+ "\n"
86
+ "def check_constant_equality(content: str, file_path: str) -> list[str]:\n"
87
+ " return []\n"
88
+ "\n"
89
+ "def check_behavior_named_mock(content: str, file_path: str) -> list[str]:\n"
90
+ " return []\n"
91
+ )
92
+ issues = check_module_docstring_names_public_checks(source, HOOK_INFRASTRUCTURE_PATH)
93
+ assert issues == [], f"Docstring naming every check must not flag, got: {issues!r}"
94
+
95
+
96
+ def test_should_not_flag_module_with_a_single_public_check() -> None:
97
+ source = (
98
+ '"""Skip-decorator test-quality check."""\n'
99
+ "\n"
100
+ "def check_skip_decorators(content: str, file_path: str) -> list[str]:\n"
101
+ " return []\n"
102
+ )
103
+ issues = check_module_docstring_names_public_checks(source, HOOK_INFRASTRUCTURE_PATH)
104
+ assert issues == [], f"A one-check module must not flag, got: {issues!r}"
105
+
106
+
107
+ def test_should_not_flag_multi_paragraph_module_docstring() -> None:
108
+ source = (
109
+ '"""Skip-decorator and existence-check test-quality checks.\n'
110
+ "\n"
111
+ " The roster grows over time; the audit lane reads the full prose body.\n"
112
+ ' """\n'
113
+ "\n"
114
+ "def check_skip_decorators(content: str, file_path: str) -> list[str]:\n"
115
+ " return []\n"
116
+ "\n"
117
+ "def check_behavior_named_mock(content: str, file_path: str) -> list[str]:\n"
118
+ " return []\n"
119
+ )
120
+ issues = check_module_docstring_names_public_checks(source, HOOK_INFRASTRUCTURE_PATH)
121
+ assert issues == [], f"Multi-paragraph docstrings go to the audit lane, got: {issues!r}"
122
+
123
+
124
+ def test_should_skip_module_without_docstring() -> None:
125
+ source = (
126
+ "def check_skip_decorators(content: str, file_path: str) -> list[str]:\n"
127
+ " return []\n"
128
+ "\n"
129
+ "def check_behavior_named_mock(content: str, file_path: str) -> list[str]:\n"
130
+ " return []\n"
131
+ )
132
+ issues = check_module_docstring_names_public_checks(source, HOOK_INFRASTRUCTURE_PATH)
133
+ assert issues == [], f"No-docstring modules are out of scope, got: {issues!r}"
134
+
135
+
136
+ def test_should_skip_private_check_helpers() -> None:
137
+ source = (
138
+ '"""Skip-decorator and existence-check test-quality checks."""\n'
139
+ "\n"
140
+ "def check_skip_decorators(content: str, file_path: str) -> list[str]:\n"
141
+ " return []\n"
142
+ "\n"
143
+ "def check_existence_check(content: str, file_path: str) -> list[str]:\n"
144
+ " return []\n"
145
+ "\n"
146
+ "def _check_internal_helper(content: str) -> bool:\n"
147
+ " return False\n"
148
+ )
149
+ issues = check_module_docstring_names_public_checks(source, HOOK_INFRASTRUCTURE_PATH)
150
+ assert issues == [], f"Private check helpers are not roster surface, got: {issues!r}"
151
+
152
+
153
+ def test_should_skip_test_file_for_module_roster() -> None:
154
+ issues = check_module_docstring_names_public_checks(
155
+ _registry_module_omitting_a_check(), TEST_FILE_PATH
156
+ )
157
+ assert issues == [], f"Test files exempt, got: {issues!r}"
158
+
159
+
160
+ def test_should_handle_module_roster_syntax_error_gracefully() -> None:
161
+ issues = check_module_docstring_names_public_checks("def broken(\n", HOOK_INFRASTRUCTURE_PATH)
162
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
163
+
164
+
165
+ def _module_with_drifted_tuple_enumeration() -> str:
166
+ return (
167
+ '"""Call-args plumbing detection."""\n'
168
+ "\n"
169
+ '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
170
+ "\n"
171
+ "def check_plumbing(content: str, file_path: str) -> list[str]:\n"
172
+ ' """Advise when a test asserts only on call-args plumbing.\n'
173
+ "\n"
174
+ " A body asserting on call-args plumbing (``call_args``, ``call_args_list``,\n"
175
+ " ``.kwargs``) reaches into mock internals rather than observed behavior.\n"
176
+ ' """\n'
177
+ " for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
178
+ " if each_name in content:\n"
179
+ " return [each_name]\n"
180
+ " return []\n"
181
+ )
182
+
183
+
184
+ def test_should_flag_docstring_tuple_enumeration_drift() -> None:
185
+ issues = check_docstring_tuple_enumeration_match(
186
+ _module_with_drifted_tuple_enumeration(), HOOK_INFRASTRUCTURE_PATH
187
+ )
188
+ assert any("check_plumbing" in each for each in issues), (
189
+ f"Expected the drifted enumeration to flag, got: {issues!r}"
190
+ )
191
+ assert any("kwargs" in each for each in issues), (
192
+ f"Expected the docstring-only token named in the message, got: {issues!r}"
193
+ )
194
+ assert any("called" in each for each in issues), (
195
+ f"Expected a tuple-only member named in the message, got: {issues!r}"
196
+ )
197
+ assert len(issues) == 1
198
+
199
+
200
+ def test_should_not_flag_docstring_tuple_enumeration_match() -> None:
201
+ source = (
202
+ '"""Call-args plumbing detection."""\n'
203
+ "\n"
204
+ '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
205
+ "\n"
206
+ "def check_plumbing(content: str, file_path: str) -> list[str]:\n"
207
+ ' """Advise when a test asserts only on call-args plumbing.\n'
208
+ "\n"
209
+ " A body asserting on call-args plumbing (``call_args``, ``call_args_list``,\n"
210
+ " ``called``, ``call_count``) reaches into mock internals.\n"
211
+ ' """\n'
212
+ " for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
213
+ " if each_name in content:\n"
214
+ " return [each_name]\n"
215
+ " return []\n"
216
+ )
217
+ issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
218
+ assert issues == [], f"Matching enumeration must not flag, got: {issues!r}"
219
+
220
+
221
+ def test_should_not_flag_docstring_without_tuple_overlap() -> None:
222
+ source = (
223
+ '"""Call-args plumbing detection."""\n'
224
+ "\n"
225
+ '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
226
+ "\n"
227
+ "def check_plumbing(content: str, file_path: str) -> list[str]:\n"
228
+ ' """Advise when a test asserts only on mock internals.\n'
229
+ "\n"
230
+ " A body reading ``foo``, ``bar``, ``baz`` is unrelated to the tuple.\n"
231
+ ' """\n'
232
+ " for each_name in _CALL_ARGS_PLUMBING_ATTRIBUTES:\n"
233
+ " if each_name in content:\n"
234
+ " return [each_name]\n"
235
+ " return []\n"
236
+ )
237
+ issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
238
+ assert issues == [], f"A docstring not naming the tuple must not flag, got: {issues!r}"
239
+
240
+
241
+ def test_should_not_flag_when_function_does_not_reference_the_tuple() -> None:
242
+ source = (
243
+ '"""Call-args plumbing detection."""\n'
244
+ "\n"
245
+ '_CALL_ARGS_PLUMBING_ATTRIBUTES = ("call_args", "call_args_list", "called", "call_count")\n'
246
+ "\n"
247
+ "def check_unrelated(content: str, file_path: str) -> list[str]:\n"
248
+ ' """Advise on ``call_args`` and ``call_args_list`` usage in tests."""\n'
249
+ " return [content[:0]]\n"
250
+ )
251
+ issues = check_docstring_tuple_enumeration_match(source, HOOK_INFRASTRUCTURE_PATH)
252
+ assert issues == [], f"A function not reading the tuple must not flag, got: {issues!r}"
253
+
254
+
255
+ def test_should_handle_tuple_enumeration_syntax_error_gracefully() -> None:
256
+ issues = check_docstring_tuple_enumeration_match("def broken(\n", HOOK_INFRASTRUCTURE_PATH)
257
+ assert issues == [], f"Syntax error must yield no issues, got: {issues!r}"
258
+
259
+
260
+ def test_validate_content_surfaces_module_roster_drift() -> None:
261
+ issues = validate_content(
262
+ _registry_module_omitting_a_check(), HOOK_INFRASTRUCTURE_PATH, old_content=""
263
+ )
264
+ matching_issues = [
265
+ each for each in issues if "check_behavior_named_mock" in each and "docstring" in each
266
+ ]
267
+ assert matching_issues, (
268
+ f"Expected validate_content to surface the module-roster drift, got: {issues!r}"
269
+ )
270
+
271
+
272
+ def test_validate_content_surfaces_tuple_enumeration_drift() -> None:
273
+ issues = validate_content(
274
+ _module_with_drifted_tuple_enumeration(), HOOK_INFRASTRUCTURE_PATH, old_content=""
275
+ )
276
+ matching_issues = [each for each in issues if "check_plumbing" in each and "enumerat" in each]
277
+ assert matching_issues, (
278
+ f"Expected validate_content to surface the tuple-enumeration drift, got: {issues!r}"
279
+ )