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.
Files changed (46) hide show
  1. package/audit-rubrics/category_rubrics/category-k-codebase-conflicts.md +1 -0
  2. package/bin/install.mjs +1 -0
  3. package/bin/install.test.mjs +3 -2
  4. package/hooks/blocking/CLAUDE.md +5 -2
  5. package/hooks/blocking/code_rules_dead_module_constant.py +215 -59
  6. package/hooks/blocking/code_rules_dead_split_branch.py +225 -0
  7. package/hooks/blocking/code_rules_docstrings.py +951 -6
  8. package/hooks/blocking/code_rules_enforcer.py +64 -0
  9. package/hooks/blocking/code_rules_naming_collection.py +76 -1
  10. package/hooks/blocking/code_rules_paired_test.py +517 -0
  11. package/hooks/blocking/code_rules_string_magic.py +71 -1
  12. package/hooks/blocking/code_rules_test_assertions.py +159 -1
  13. package/hooks/blocking/convergence_gate_blocker.py +24 -15
  14. package/hooks/blocking/env_var_table_code_drift_blocker.py +475 -0
  15. package/hooks/blocking/package_inventory_stale_blocker.py +54 -15
  16. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +89 -2
  17. package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +105 -0
  18. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +129 -0
  19. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +198 -0
  20. package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +262 -0
  21. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +115 -0
  22. package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +226 -0
  23. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +160 -0
  24. package/hooks/blocking/test_code_rules_enforcer_module_docstring_data_schema_scope.py +82 -0
  25. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +339 -0
  26. package/hooks/blocking/test_code_rules_enforcer_polarity_name_contradiction.py +76 -0
  27. package/hooks/blocking/test_code_rules_enforcer_vacuous_cleanup_assertion.py +132 -0
  28. package/hooks/blocking/test_code_rules_enforcer_whitespace_indentation_magic.py +74 -0
  29. package/hooks/blocking/test_convergence_gate_blocker.py +71 -0
  30. package/hooks/blocking/test_env_var_table_code_drift_blocker.py +94 -0
  31. package/hooks/blocking/test_package_inventory_stale_blocker.py +46 -0
  32. package/hooks/blocking/test_pre_tool_use_dispatcher.py +7 -7
  33. package/hooks/hooks_constants/CLAUDE.md +2 -0
  34. package/hooks/hooks_constants/blocking_check_limits.py +102 -0
  35. package/hooks/hooks_constants/code_rules_enforcer_constants.py +28 -0
  36. package/hooks/hooks_constants/env_var_table_code_drift_constants.py +64 -0
  37. package/hooks/hooks_constants/package_inventory_stale_blocker_constants.py +20 -9
  38. package/hooks/hooks_constants/paired_test_coverage_constants.py +35 -0
  39. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  40. package/package.json +1 -1
  41. package/rules/CLAUDE.md +2 -0
  42. package/rules/docstring-prose-matches-implementation.md +56 -53
  43. package/rules/env-var-table-code-drift.md +24 -0
  44. package/rules/file-global-constants.md +2 -2
  45. package/rules/package-inventory-stale-entry.md +4 -4
  46. 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, and loop-variable naming checks."""
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