claude-dev-env 1.77.0 → 1.79.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.
- package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
- package/bin/install.mjs +1 -0
- package/bin/install.test.mjs +3 -2
- package/hooks/blocking/CLAUDE.md +5 -2
- package/hooks/blocking/code_rules_dead_module_constant.py +215 -59
- package/hooks/blocking/code_rules_dead_split_branch.py +225 -0
- package/hooks/blocking/code_rules_docstrings.py +951 -6
- package/hooks/blocking/code_rules_enforcer.py +64 -0
- package/hooks/blocking/code_rules_naming_collection.py +76 -1
- package/hooks/blocking/code_rules_paired_test.py +517 -0
- package/hooks/blocking/code_rules_string_magic.py +71 -1
- package/hooks/blocking/code_rules_test_assertions.py +159 -1
- package/hooks/blocking/convergence_gate_blocker.py +24 -15
- package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
- package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +89 -2
- package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +105 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +262 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +226 -0
- package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
- package/hooks/blocking/test_code_rules_enforcer_paired_test.py +339 -0
- package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
- package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
- package/hooks/blocking/test_code_rules_enforcer_whitespace_indentation_magic.py +74 -0
- package/hooks/blocking/test_convergence_gate_blocker.py +71 -0
- package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
- package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/blocking_check_limits.py +102 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
- package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
- package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
- package/hooks/hooks_constants/paired_test_coverage_constants.py +35 -0
- package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
- package/package.json +1 -1
- package/rules/CLAUDE.md +2 -0
- package/rules/docstring-prose-matches-implementation.md +56 -53
- package/rules/env-var-table-code-drift.md +24 -0
- package/rules/file-global-constants.md +2 -2
- package/rules/package-inventory-stale-entry.md +4 -4
- package/rules/paired-test-coverage.md +35 -0
|
@@ -64,22 +64,32 @@ from code_rules_dead_dataclass_field import ( # noqa: E402
|
|
|
64
64
|
from code_rules_dead_module_constant import ( # noqa: E402
|
|
65
65
|
check_dead_module_constants,
|
|
66
66
|
)
|
|
67
|
+
from code_rules_dead_split_branch import ( # noqa: E402
|
|
68
|
+
check_dead_split_truthiness_branch,
|
|
69
|
+
)
|
|
67
70
|
from code_rules_docstrings import ( # noqa: E402
|
|
68
71
|
check_class_docstring_names_public_methods,
|
|
69
72
|
check_docstring_args_match_signature,
|
|
70
73
|
check_docstring_args_single_line_scope_vs_span,
|
|
71
74
|
check_docstring_cardinal_count_matches_constant_family,
|
|
75
|
+
check_docstring_documents_unreferenced_parameter,
|
|
72
76
|
check_docstring_fallback_branch_coverage,
|
|
77
|
+
check_docstring_field_runmode_outcome,
|
|
73
78
|
check_docstring_format,
|
|
79
|
+
check_docstring_length_constant_superlative_vs_exact_gate,
|
|
74
80
|
check_docstring_names_undefined_constant,
|
|
75
81
|
check_docstring_no_consumer_claim,
|
|
76
82
|
check_docstring_no_inline_literal_claim,
|
|
83
|
+
check_docstring_no_network_claim_with_metadata_access,
|
|
84
|
+
check_docstring_punctuation_mark_enumeration_coverage,
|
|
85
|
+
check_docstring_raises_unraisable_largezipfile,
|
|
77
86
|
check_docstring_returns_plural_cardinality,
|
|
78
87
|
check_docstring_runon_sentence,
|
|
79
88
|
check_docstring_step_enumeration_dispatch_coverage,
|
|
80
89
|
check_docstring_tuple_enumeration_match,
|
|
81
90
|
check_docstring_unguarded_malformed_payload_claim,
|
|
82
91
|
check_module_docstring_names_public_checks,
|
|
92
|
+
check_module_docstring_scope_omits_data_schema_constants,
|
|
83
93
|
)
|
|
84
94
|
from code_rules_duplicate_body import ( # noqa: E402
|
|
85
95
|
advise_cross_skill_duplicate_helper,
|
|
@@ -107,6 +117,7 @@ from code_rules_mock_completeness import ( # noqa: E402
|
|
|
107
117
|
from code_rules_naming_collection import ( # noqa: E402
|
|
108
118
|
check_collection_prefix,
|
|
109
119
|
check_loop_variable_naming,
|
|
120
|
+
check_polarity_name_contradiction,
|
|
110
121
|
check_stuttering_collection_prefix,
|
|
111
122
|
)
|
|
112
123
|
from code_rules_optional_params import ( # noqa: E402
|
|
@@ -116,6 +127,10 @@ from code_rules_optional_params import ( # noqa: E402
|
|
|
116
127
|
from code_rules_orphan_css_class import ( # noqa: E402
|
|
117
128
|
check_orphan_css_classes,
|
|
118
129
|
)
|
|
130
|
+
from code_rules_paired_test import ( # noqa: E402
|
|
131
|
+
check_public_function_missing_paired_test,
|
|
132
|
+
check_test_file_omits_module_public_function,
|
|
133
|
+
)
|
|
119
134
|
from code_rules_paths_syspath import ( # noqa: E402
|
|
120
135
|
check_hardcoded_user_paths,
|
|
121
136
|
check_sys_path_insert_deduplication_guard,
|
|
@@ -131,6 +146,7 @@ from code_rules_string_magic import ( # noqa: E402
|
|
|
131
146
|
check_inline_literal_collections,
|
|
132
147
|
check_inline_tuple_string_magic,
|
|
133
148
|
check_string_literal_magic,
|
|
149
|
+
check_whitespace_indentation_magic,
|
|
134
150
|
)
|
|
135
151
|
from code_rules_test_assertions import ( # noqa: E402
|
|
136
152
|
check_constant_equality_tests,
|
|
@@ -138,6 +154,7 @@ from code_rules_test_assertions import ( # noqa: E402
|
|
|
138
154
|
check_flag_gated_scenario_test_naming,
|
|
139
155
|
check_skip_decorators_in_tests,
|
|
140
156
|
check_stale_test_name_target,
|
|
157
|
+
check_vacuous_cleanup_assertion_tests,
|
|
141
158
|
)
|
|
142
159
|
from code_rules_test_branching_except import ( # noqa: E402
|
|
143
160
|
check_bare_except,
|
|
@@ -285,8 +302,16 @@ def validate_content(
|
|
|
285
302
|
all_issues.extend(check_boundary_types(effective_content, file_path))
|
|
286
303
|
all_issues.extend(check_docstring_format(effective_content, file_path))
|
|
287
304
|
all_issues.extend(check_docstring_args_match_signature(effective_content, file_path))
|
|
305
|
+
all_issues.extend(
|
|
306
|
+
check_docstring_documents_unreferenced_parameter(effective_content, file_path)
|
|
307
|
+
)
|
|
288
308
|
all_issues.extend(check_docstring_fallback_branch_coverage(effective_content, file_path))
|
|
289
309
|
all_issues.extend(check_docstring_no_consumer_claim(effective_content, file_path))
|
|
310
|
+
all_issues.extend(
|
|
311
|
+
check_docstring_no_network_claim_with_metadata_access(
|
|
312
|
+
effective_content, file_path
|
|
313
|
+
)
|
|
314
|
+
)
|
|
290
315
|
all_issues.extend(
|
|
291
316
|
check_docstring_unguarded_malformed_payload_claim(
|
|
292
317
|
effective_content, file_path
|
|
@@ -302,9 +327,19 @@ def validate_content(
|
|
|
302
327
|
all_issues.extend(
|
|
303
328
|
check_module_docstring_names_public_checks(effective_content, file_path)
|
|
304
329
|
)
|
|
330
|
+
all_issues.extend(
|
|
331
|
+
check_module_docstring_scope_omits_data_schema_constants(
|
|
332
|
+
effective_content, file_path
|
|
333
|
+
)
|
|
334
|
+
)
|
|
305
335
|
all_issues.extend(
|
|
306
336
|
check_docstring_tuple_enumeration_match(effective_content, file_path)
|
|
307
337
|
)
|
|
338
|
+
all_issues.extend(
|
|
339
|
+
check_docstring_punctuation_mark_enumeration_coverage(
|
|
340
|
+
effective_content, file_path
|
|
341
|
+
)
|
|
342
|
+
)
|
|
308
343
|
all_issues.extend(
|
|
309
344
|
check_docstring_step_enumeration_dispatch_coverage(
|
|
310
345
|
effective_content, file_path
|
|
@@ -313,6 +348,17 @@ def validate_content(
|
|
|
313
348
|
all_issues.extend(
|
|
314
349
|
check_docstring_returns_plural_cardinality(effective_content, file_path)
|
|
315
350
|
)
|
|
351
|
+
all_issues.extend(
|
|
352
|
+
check_docstring_length_constant_superlative_vs_exact_gate(
|
|
353
|
+
effective_content, file_path
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
all_issues.extend(
|
|
357
|
+
check_docstring_field_runmode_outcome(effective_content, file_path)
|
|
358
|
+
)
|
|
359
|
+
all_issues.extend(
|
|
360
|
+
check_docstring_raises_unraisable_largezipfile(effective_content, file_path)
|
|
361
|
+
)
|
|
316
362
|
all_issues.extend(
|
|
317
363
|
check_docstring_cardinal_count_matches_constant_family(
|
|
318
364
|
effective_content, file_path
|
|
@@ -351,6 +397,7 @@ def validate_content(
|
|
|
351
397
|
)
|
|
352
398
|
all_issues.extend(check_existence_check_tests(content, file_path))
|
|
353
399
|
all_issues.extend(check_constant_equality_tests(content, file_path))
|
|
400
|
+
all_issues.extend(check_vacuous_cleanup_assertion_tests(content, file_path))
|
|
354
401
|
all_issues.extend(check_stale_test_name_target(content, file_path))
|
|
355
402
|
check_flag_gated_scenario_test_naming(content, file_path)
|
|
356
403
|
all_issues.extend(check_unused_optional_parameters(content, file_path))
|
|
@@ -373,6 +420,7 @@ def validate_content(
|
|
|
373
420
|
all_issues.extend(
|
|
374
421
|
check_dead_module_constants(content, file_path, full_file_content)
|
|
375
422
|
)
|
|
423
|
+
all_issues.extend(check_dead_split_truthiness_branch(content, file_path))
|
|
376
424
|
all_issues.extend(check_library_print(content, file_path))
|
|
377
425
|
all_issues.extend(check_parameter_annotations(content, file_path))
|
|
378
426
|
all_issues.extend(check_known_pytest_fixture_annotations(content, file_path))
|
|
@@ -388,10 +436,26 @@ def validate_content(
|
|
|
388
436
|
defer_scope_to_caller,
|
|
389
437
|
)
|
|
390
438
|
)
|
|
439
|
+
all_issues.extend(
|
|
440
|
+
check_public_function_missing_paired_test(
|
|
441
|
+
effective_content,
|
|
442
|
+
file_path,
|
|
443
|
+
all_changed_lines,
|
|
444
|
+
defer_scope_to_caller,
|
|
445
|
+
)
|
|
446
|
+
)
|
|
447
|
+
all_issues.extend(
|
|
448
|
+
check_test_file_omits_module_public_function(
|
|
449
|
+
effective_content,
|
|
450
|
+
file_path,
|
|
451
|
+
)
|
|
452
|
+
)
|
|
391
453
|
all_issues.extend(check_loop_variable_naming(content, file_path))
|
|
454
|
+
all_issues.extend(check_polarity_name_contradiction(content, file_path))
|
|
392
455
|
all_issues.extend(check_inline_literal_collections(content, file_path))
|
|
393
456
|
all_issues.extend(check_inline_tuple_string_magic(content, file_path))
|
|
394
457
|
all_issues.extend(check_string_literal_magic(content, file_path))
|
|
458
|
+
all_issues.extend(check_whitespace_indentation_magic(content, file_path))
|
|
395
459
|
all_issues.extend(check_orphan_css_classes(effective_content, file_path))
|
|
396
460
|
check_incomplete_mocks(content, file_path)
|
|
397
461
|
check_duplicated_format_patterns(content, file_path)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""Collection-prefix, stuttering-prefix,
|
|
1
|
+
"""Collection-prefix, stuttering-prefix, loop-variable, and polarity-name-contradiction naming checks."""
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
|
+
import re
|
|
4
5
|
import sys
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -11,6 +12,7 @@ if _blocking_directory not in sys.path:
|
|
|
11
12
|
if _hooks_directory not in sys.path:
|
|
12
13
|
sys.path.insert(0, _hooks_directory)
|
|
13
14
|
|
|
15
|
+
from code_rules_boolean_mustcheck import _called_terminal_name # noqa: E402
|
|
14
16
|
from code_rules_shared import ( # noqa: E402
|
|
15
17
|
_collect_annotated_arguments,
|
|
16
18
|
_collect_target_names,
|
|
@@ -22,11 +24,13 @@ from code_rules_shared import ( # noqa: E402
|
|
|
22
24
|
from hooks_constants.code_rules_enforcer_constants import ( # noqa: E402
|
|
23
25
|
ALL_COLLECTION_TYPE_NAMES,
|
|
24
26
|
ALL_LOOP_INDEX_LETTER_EXEMPTIONS,
|
|
27
|
+
ALL_POLARITY_ANTONYM_TOKEN_PAIRS,
|
|
25
28
|
ALL_SUBSCRIPT_ONLY_COLLECTION_TYPE_NAMES,
|
|
26
29
|
ALL_UNION_TYPING_NAMES,
|
|
27
30
|
BARE_EACH_TOKEN,
|
|
28
31
|
COLLECTION_BY_NAME_PATTERN,
|
|
29
32
|
EACH_PREFIX,
|
|
33
|
+
POLARITY_TOKEN_BOUNDARY_PATTERN,
|
|
30
34
|
UPPER_SNAKE_CONSTANT_PATTERN,
|
|
31
35
|
)
|
|
32
36
|
from hooks_constants.stuttering_check_config import ( # noqa: E402
|
|
@@ -262,3 +266,74 @@ def check_loop_variable_naming(content: str, file_path: str) -> list[str]:
|
|
|
262
266
|
f"Line {each_name_node.lineno}: loop variable {target_name!r} - prefix with each_ (CODE_RULES §5)"
|
|
263
267
|
)
|
|
264
268
|
return issues
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _name_carries_token(name: str, token: str) -> bool:
|
|
272
|
+
"""True when name carries token as a whole underscore-delimited word."""
|
|
273
|
+
return re.search(POLARITY_TOKEN_BOUNDARY_PATTERN % re.escape(token), name) is not None
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _contradicting_polarity_pair(target_name: str, called_name: str) -> tuple[str, str] | None:
|
|
277
|
+
"""The (target_token, called_token) antonym pair when target and callee assert opposite polarity."""
|
|
278
|
+
target_lower = target_name.lower()
|
|
279
|
+
called_lower = called_name.lower()
|
|
280
|
+
for each_positive_token, each_negative_token in ALL_POLARITY_ANTONYM_TOKEN_PAIRS:
|
|
281
|
+
if _name_carries_token(target_lower, each_positive_token) and _name_carries_token(
|
|
282
|
+
called_lower, each_negative_token
|
|
283
|
+
):
|
|
284
|
+
return each_positive_token, each_negative_token
|
|
285
|
+
if _name_carries_token(target_lower, each_negative_token) and _name_carries_token(
|
|
286
|
+
called_lower, each_positive_token
|
|
287
|
+
):
|
|
288
|
+
return each_negative_token, each_positive_token
|
|
289
|
+
return None
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def check_polarity_name_contradiction(content: str, file_path: str) -> list[str]:
|
|
293
|
+
"""Flag a boolean assignment whose target and callee assert opposite polarity.
|
|
294
|
+
|
|
295
|
+
Catches the self-contradicting statement ``is_inside_allowed =
|
|
296
|
+
_point_hits_any_forbidden(...)`` — a target name carrying one polarity token
|
|
297
|
+
(``allowed``) assigned directly from a single call whose callee name carries
|
|
298
|
+
the antonym token (``forbidden``). One side lies about the behavior, so the
|
|
299
|
+
reader cannot trust either name. Rename the callee to a neutral form that
|
|
300
|
+
reads truthfully at every call site.
|
|
301
|
+
"""
|
|
302
|
+
if is_test_file(file_path):
|
|
303
|
+
return []
|
|
304
|
+
if is_workflow_registry_file(file_path) or is_migration_file(file_path):
|
|
305
|
+
return []
|
|
306
|
+
try:
|
|
307
|
+
tree = ast.parse(content)
|
|
308
|
+
except SyntaxError:
|
|
309
|
+
return []
|
|
310
|
+
issues: list[str] = []
|
|
311
|
+
for each_node in ast.walk(tree):
|
|
312
|
+
target_name: str | None = None
|
|
313
|
+
value_node: ast.expr | None = None
|
|
314
|
+
target_line = 0
|
|
315
|
+
if isinstance(each_node, ast.Assign) and len(each_node.targets) == 1 and isinstance(
|
|
316
|
+
each_node.targets[0], ast.Name
|
|
317
|
+
):
|
|
318
|
+
target_name = each_node.targets[0].id
|
|
319
|
+
value_node = each_node.value
|
|
320
|
+
target_line = each_node.lineno
|
|
321
|
+
elif isinstance(each_node, ast.AnnAssign) and isinstance(each_node.target, ast.Name):
|
|
322
|
+
target_name = each_node.target.id
|
|
323
|
+
value_node = each_node.value
|
|
324
|
+
target_line = each_node.lineno
|
|
325
|
+
if target_name is None or not isinstance(value_node, ast.Call):
|
|
326
|
+
continue
|
|
327
|
+
called_name = _called_terminal_name(value_node)
|
|
328
|
+
if called_name is None:
|
|
329
|
+
continue
|
|
330
|
+
contradicting_pair = _contradicting_polarity_pair(target_name, called_name)
|
|
331
|
+
if contradicting_pair is None:
|
|
332
|
+
continue
|
|
333
|
+
target_token, called_token = contradicting_pair
|
|
334
|
+
issues.append(
|
|
335
|
+
f"Line {target_line}: {target_name!r} (says {target_token!r}) is assigned from"
|
|
336
|
+
f" {called_name!r} (says {called_token!r}) - the name contradicts the value; rename the"
|
|
337
|
+
f" callee to a neutral form so each call site reads truthfully (CODE_RULES §5)"
|
|
338
|
+
)
|
|
339
|
+
return issues
|