claude-dev-env 1.50.1 → 1.50.2

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 (63) hide show
  1. package/hooks/blocking/code_rules_annotations_length.py +167 -0
  2. package/hooks/blocking/code_rules_banned_identifiers.py +385 -0
  3. package/hooks/blocking/code_rules_boolean_mustcheck.py +350 -0
  4. package/hooks/blocking/code_rules_comments.py +337 -0
  5. package/hooks/blocking/code_rules_constants_config.py +252 -0
  6. package/hooks/blocking/code_rules_docstrings.py +308 -0
  7. package/hooks/blocking/code_rules_enforcer.py +98 -5807
  8. package/hooks/blocking/code_rules_imports_logging.py +276 -0
  9. package/hooks/blocking/code_rules_magic_values.py +180 -0
  10. package/hooks/blocking/code_rules_mock_completeness.py +295 -0
  11. package/hooks/blocking/code_rules_naming_collection.py +264 -0
  12. package/hooks/blocking/code_rules_optional_params.py +288 -0
  13. package/hooks/blocking/code_rules_paths_syspath.py +186 -0
  14. package/hooks/blocking/code_rules_probe_chains.py +305 -0
  15. package/hooks/blocking/code_rules_probe_detection.py +257 -0
  16. package/hooks/blocking/code_rules_probe_recording.py +225 -0
  17. package/hooks/blocking/code_rules_scope_binding.py +151 -0
  18. package/hooks/blocking/code_rules_shared.py +301 -0
  19. package/hooks/blocking/code_rules_string_magic.py +207 -0
  20. package/hooks/blocking/code_rules_test_assertions.py +226 -0
  21. package/hooks/blocking/code_rules_test_branching_except.py +181 -0
  22. package/hooks/blocking/code_rules_test_isolation.py +341 -0
  23. package/hooks/blocking/code_rules_type_escape.py +341 -0
  24. package/hooks/blocking/code_rules_typeddict_stub.py +305 -0
  25. package/hooks/blocking/code_rules_unused_imports.py +256 -0
  26. package/hooks/blocking/tdd_enforcer.py +31 -0
  27. package/hooks/blocking/test_code_rules_constants_config.py +26 -0
  28. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +5 -2
  29. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -5
  30. package/hooks/blocking/test_code_rules_enforcer_comment_string_awareness.py +21 -15
  31. package/hooks/blocking/test_code_rules_enforcer_config_path.py +20 -16
  32. package/hooks/blocking/test_code_rules_enforcer_exempt_marker_chained.py +4 -2
  33. package/hooks/blocking/test_code_rules_enforcer_function_length.py +18 -13
  34. package/hooks/blocking/test_code_rules_enforcer_hardcoded_user_path.py +1 -2
  35. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +22 -12
  36. package/hooks/blocking/test_code_rules_enforcer_split_annotations_length.py +55 -0
  37. package/hooks/blocking/test_code_rules_enforcer_split_banned.py +170 -0
  38. package/hooks/blocking/test_code_rules_enforcer_split_comments.py +60 -0
  39. package/hooks/blocking/test_code_rules_enforcer_split_config_path.py +52 -0
  40. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +236 -0
  41. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +296 -0
  42. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +238 -0
  43. package/hooks/blocking/test_code_rules_enforcer_split_isolation_1.py +271 -0
  44. package/hooks/blocking/test_code_rules_enforcer_split_isolation_2.py +283 -0
  45. package/hooks/blocking/test_code_rules_enforcer_split_isolation_3.py +268 -0
  46. package/hooks/blocking/test_code_rules_enforcer_split_isolation_4.py +85 -0
  47. package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +303 -0
  48. package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +111 -0
  49. package/hooks/blocking/test_code_rules_enforcer_split_mustcheck.py +87 -0
  50. package/hooks/blocking/test_code_rules_enforcer_split_naming.py +107 -0
  51. package/hooks/blocking/test_code_rules_enforcer_split_optional_params.py +325 -0
  52. package/hooks/blocking/test_code_rules_enforcer_split_paths_syspath.py +110 -0
  53. package/hooks/blocking/test_code_rules_enforcer_split_shared.py +44 -0
  54. package/hooks/blocking/test_code_rules_enforcer_split_string_magic.py +55 -0
  55. package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +56 -0
  56. package/hooks/blocking/test_code_rules_enforcer_todo_markers.py +21 -15
  57. package/hooks/blocking/test_code_rules_paths_syspath.py +26 -0
  58. package/hooks/blocking/test_tdd_enforcer.py +116 -0
  59. package/hooks/hooks_constants/blocking_check_limits.py +3 -0
  60. package/hooks/hooks_constants/code_rules_enforcer_constants.py +8 -0
  61. package/hooks/hooks_constants/sys_path_insert_constants.py +1 -0
  62. package/package.json +1 -1
  63. package/hooks/blocking/test_code_rules_enforcer.py +0 -2669
@@ -574,3 +574,119 @@ def test_is_inside_dotclaude_segment_helper_matches_only_exact_segments() -> Non
574
574
  assert _PRODUCTION_MODULE._is_inside_dotclaude_segment("C:\\Users\\dev\\.claude\\agent.py") is True
575
575
  assert _PRODUCTION_MODULE._is_inside_dotclaude_segment("/src/my.claude.helpers.py") is False
576
576
  assert _PRODUCTION_MODULE._is_inside_dotclaude_segment("/src/app/service.py") is False
577
+
578
+
579
+ def test_should_offer_split_family_test_files_as_candidates_for_code_rules_module(
580
+ tmp_path: Path,
581
+ ) -> None:
582
+ sandbox = _sandbox(tmp_path)
583
+ production_module = sandbox / "code_rules_magic_values.py"
584
+ string_magic_family_test = sandbox / "test_code_rules_enforcer_split_string_magic.py"
585
+ string_magic_family_test.write_text("def test_string_magic(): pass\n")
586
+ banned_family_test = sandbox / "test_code_rules_enforcer_split_banned.py"
587
+ banned_family_test.write_text("def test_banned(): pass\n")
588
+
589
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
590
+
591
+ assert string_magic_family_test in all_candidates
592
+ assert banned_family_test in all_candidates
593
+
594
+
595
+ def test_should_keep_plain_stem_candidates_first_for_code_rules_module(
596
+ tmp_path: Path,
597
+ ) -> None:
598
+ sandbox = _sandbox(tmp_path)
599
+ production_module = sandbox / "code_rules_magic_values.py"
600
+ family_test = sandbox / "test_code_rules_enforcer_split_string_magic.py"
601
+ family_test.write_text("def test_string_magic(): pass\n")
602
+
603
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
604
+
605
+ assert all_candidates[0] == sandbox / "test_code_rules_magic_values.py"
606
+ assert all_candidates[1] == sandbox / "code_rules_magic_values_test.py"
607
+
608
+
609
+ def test_should_not_offer_split_family_candidates_for_non_code_rules_module(
610
+ tmp_path: Path,
611
+ ) -> None:
612
+ sandbox = _sandbox(tmp_path)
613
+ production_module = sandbox / "orders.py"
614
+ family_test = sandbox / "test_code_rules_enforcer_split_string_magic.py"
615
+ family_test.write_text("def test_string_magic(): pass\n")
616
+
617
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
618
+
619
+ assert family_test not in all_candidates
620
+
621
+
622
+ def test_should_add_no_split_family_candidates_when_directory_has_none(
623
+ tmp_path: Path,
624
+ ) -> None:
625
+ sandbox = _sandbox(tmp_path)
626
+ production_module = sandbox / "code_rules_magic_values.py"
627
+
628
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
629
+
630
+ expected_stem_candidates = [
631
+ sandbox / "test_code_rules_magic_values.py",
632
+ sandbox / "code_rules_magic_values_test.py",
633
+ ]
634
+ assert all_candidates == expected_stem_candidates
635
+
636
+
637
+ def test_should_not_offer_family_candidates_for_code_ruleset_stem(
638
+ tmp_path: Path,
639
+ ) -> None:
640
+ sandbox = _sandbox(tmp_path)
641
+ production_module = sandbox / "code_ruleset.py"
642
+ family_test = sandbox / "test_code_rules_enforcer_split_example.py"
643
+ family_test.write_text("def test_detects_example(): pass\n")
644
+
645
+ all_candidates = _PRODUCTION_MODULE.candidate_test_paths_for(production_module)
646
+
647
+ expected_stem_candidates = [
648
+ sandbox / "test_code_ruleset.py",
649
+ sandbox / "code_ruleset_test.py",
650
+ ]
651
+ assert all_candidates == expected_stem_candidates
652
+ assert family_test not in all_candidates
653
+
654
+
655
+ def test_should_allow_code_rules_edit_when_fresh_split_family_sibling_exists(
656
+ tmp_path: Path,
657
+ ) -> None:
658
+ sandbox = _sandbox(tmp_path)
659
+ production_module = sandbox / "code_rules_example.py"
660
+ production_module.write_text("def detect() -> None:\n return None\n")
661
+ family_test = sandbox / "test_code_rules_enforcer_split_example_concern.py"
662
+ family_test.write_text("def test_detects_example(): pass\n")
663
+
664
+ payload = _make_edit_payload(
665
+ production_module,
666
+ old_string="return None",
667
+ new_string="return None # adjusted",
668
+ )
669
+ completed = _run_hook_with_payload(payload)
670
+
671
+ assert _decision_from(completed) == "allow"
672
+
673
+
674
+ def test_should_deny_code_rules_edit_when_split_family_sibling_is_stale(
675
+ tmp_path: Path,
676
+ ) -> None:
677
+ sandbox = _sandbox(tmp_path)
678
+ production_module = sandbox / "code_rules_example.py"
679
+ production_module.write_text("def detect() -> None:\n return None\n")
680
+ family_test = sandbox / "test_code_rules_enforcer_split_example_concern.py"
681
+ family_test.write_text("def test_detects_example(): pass\n")
682
+ stale_timestamp = time.time() - STALE_MTIME_OFFSET_SECONDS
683
+ os.utime(family_test, (stale_timestamp, stale_timestamp))
684
+
685
+ payload = _make_edit_payload(
686
+ production_module,
687
+ old_string="return None",
688
+ new_string="return None # adjusted",
689
+ )
690
+ completed = _run_hook_with_payload(payload)
691
+
692
+ assert _decision_from(completed) == "deny"
@@ -20,6 +20,9 @@ MAX_DOCSTRING_ARGS_SIGNATURE_ISSUES: int = 5
20
20
  MAX_IGNORED_MUST_CHECK_RETURN_ISSUES: int = 5
21
21
  MAX_TYPE_ESCAPE_HATCH_ISSUES: int = 5
22
22
  MAX_THIN_WRAPPER_ISSUES: int = 1
23
+ MAX_LOGGING_FSTRING_ISSUES: int = 3
24
+ MAX_WINDOWS_API_NONE_ISSUES: int = 3
25
+ MAX_E2E_TEST_NAMING_ISSUES: int = 3
23
26
  DOCSTRING_TRIVIAL_FUNCTION_BODY_LINE_LIMIT: int = 3
24
27
 
25
28
  ALL_BARE_EXCEPT_BANNED_HANDLER_NAMES: frozenset[str] = frozenset({"Exception", "BaseException"})
@@ -79,6 +79,14 @@ NOT_INSIDE_TYPE_CHECKING_BLOCK = -1
79
79
  TRIPLE_QUOTE_PARITY_DIVISOR = 2
80
80
  TRIPLE_DOUBLE_QUOTE_DELIMITER = '"""'
81
81
  TRIPLE_SINGLE_QUOTE_DELIMITER = "'''"
82
+ MAX_MAGIC_VALUE_ISSUES = 3
83
+ STRING_LITERAL_QUOTE_PAIR_LENGTH = 2
84
+ MINIMUM_FSTRING_LITERAL_LENGTH = 2
85
+ MAX_FSTRING_STRUCTURAL_LITERAL_ISSUES = 100
86
+ ALL_ALLOWED_MAGIC_NUMBER_LITERALS: frozenset[str] = frozenset({"0", "1", "-1", "0.0", "1.0"})
87
+ ALL_NON_MAGIC_FSTRING_STRIPPED_VALUES: frozenset[str] = frozenset({"", "True", "False"})
88
+ DUPLICATED_FORMAT_MINIMUM_REPETITION_COUNT = 3
89
+ DUPLICATED_FORMAT_MINIMUM_LITERAL_CHARACTER_COUNT = 5
82
90
  FILE_GLOBAL_UPPER_SNAKE_PATTERN = re.compile(r"^_?[A-Z][A-Z0-9_]*$")
83
91
 
84
92
  ALL_COLLECTION_TYPE_NAMES: frozenset[str] = frozenset({
@@ -2,3 +2,4 @@
2
2
 
3
3
  MAX_SYS_PATH_INSERT_ISSUES: int = 25
4
4
  SYS_PATH_INSERT_GUIDANCE: str = "guard with `if <path> not in sys.path:` to avoid pushing the same entry on every reload"
5
+ SYS_PATH_INSERT_MINIMUM_ARGUMENT_COUNT: int = 2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.50.1",
3
+ "version": "1.50.2",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {