claude-dev-env 2.30.1 → 2.31.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 (118) hide show
  1. package/.agents/agents/pr-description-writer.md +1 -1
  2. package/AGENTS.md +6 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +37 -0
  4. package/bin/install.prune.test.mjs +4 -2
  5. package/docs/CODE_RULES.md +11 -0
  6. package/hooks/advisory/migration_safety_advisor.py +25 -1
  7. package/hooks/advisory/refactor_guard.py +85 -18
  8. package/hooks/advisory/test_migration_safety_advisor.py +89 -5
  9. package/hooks/advisory/test_refactor_guard.py +87 -0
  10. package/hooks/advisory/test_refactor_guard_advisory.py +72 -1
  11. package/hooks/blocking/code_rules_constants_config.py +7 -6
  12. package/hooks/blocking/code_rules_enforcer.py +57 -31
  13. package/hooks/blocking/codex_apply_patch.py +136 -21
  14. package/hooks/blocking/config/codex_apply_patch_constants.py +13 -0
  15. package/hooks/blocking/conftest.py +70 -0
  16. package/hooks/blocking/duplicate_rmtree_helper_blocker.py +26 -16
  17. package/hooks/blocking/hook_prose_detector_consistency.py +30 -20
  18. package/hooks/blocking/pii_payload_scan.py +61 -0
  19. package/hooks/blocking/pii_prevention_blocker.py +5 -0
  20. package/hooks/blocking/pr_description_writer_gate.py +263 -0
  21. package/hooks/blocking/pre_tool_use_dispatcher.py +1 -1
  22. package/hooks/blocking/sensitive_file_protector.py +18 -3
  23. package/hooks/blocking/shell_substitution_blocker.py +25 -47
  24. package/hooks/blocking/stale_comment_reference_blocker.py +94 -24
  25. package/hooks/blocking/state_description_blocker.py +39 -26
  26. package/hooks/blocking/subprocess_budget_completeness.py +17 -43
  27. package/hooks/blocking/tdd_enforcer.py +57 -14
  28. package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +11 -1
  29. package/hooks/blocking/tdd_enforcer_parts/content_hash_store.py +180 -0
  30. package/hooks/blocking/tdd_enforcer_parts/decisions.py +6 -2
  31. package/hooks/blocking/tdd_enforcer_parts/freshness.py +18 -3
  32. package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +66 -1
  33. package/hooks/blocking/tdd_enforcer_parts/tests/test_content_hash_store.py +143 -0
  34. package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +12 -0
  35. package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +63 -0
  36. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -0
  37. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +81 -2
  38. package/hooks/blocking/test_code_rules_enforcer_codex_apply_patch.py +35 -0
  39. package/hooks/blocking/test_code_rules_enforcer_config_path.py +29 -16
  40. package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +2 -0
  41. package/hooks/blocking/test_code_rules_enforcer_multi_edit.py +118 -0
  42. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +2 -0
  43. package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +2 -0
  44. package/hooks/blocking/test_code_rules_enforcer_split_constants_config.py +29 -28
  45. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +2 -0
  46. package/hooks/blocking/test_code_rules_enforcer_stage.py +2 -0
  47. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +2 -0
  48. package/hooks/blocking/test_codex_apply_patch.py +43 -0
  49. package/hooks/blocking/test_duplicate_rmtree_helper_blocker.py +68 -0
  50. package/hooks/blocking/test_hook_prose_detector_consistency.py +41 -0
  51. package/hooks/blocking/test_pii_payload_scan.py +65 -2
  52. package/hooks/blocking/test_pii_prevention_blocker.py +80 -10
  53. package/hooks/blocking/test_pr_description_writer_gate.py +216 -0
  54. package/hooks/blocking/test_pre_tool_use_dispatcher.py +215 -25
  55. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +9 -4
  56. package/hooks/blocking/test_sensitive_file_protector.py +42 -2
  57. package/hooks/blocking/test_stale_comment_reference_blocker.py +71 -0
  58. package/hooks/blocking/test_state_description_blocker.py +68 -0
  59. package/hooks/blocking/test_subprocess_budget_completeness.py +67 -0
  60. package/hooks/blocking/test_tdd_enforcer.py +182 -2
  61. package/hooks/blocking/test_windows_rmtree_blocker.py +63 -0
  62. package/hooks/blocking/test_write_existing_file_blocker.py +122 -0
  63. package/hooks/blocking/windows_rmtree_blocker.py +21 -7
  64. package/hooks/blocking/write_existing_file_blocker.py +75 -29
  65. package/hooks/conftest.py +47 -0
  66. package/hooks/hooks.json +7 -12
  67. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +3 -0
  68. package/hooks/hooks_constants/code_rules_enforcer_constants.py +4 -0
  69. package/hooks/hooks_constants/duplicate_rmtree_helper_blocker_constants.py +3 -0
  70. package/hooks/hooks_constants/hook_prose_detector_consistency_constants.py +7 -2
  71. package/hooks/hooks_constants/migration_safety_advisor_constants.py +7 -0
  72. package/hooks/hooks_constants/multi_edit_reconstruction.py +44 -3
  73. package/hooks/hooks_constants/pii_prevention_constants.py +9 -7
  74. package/hooks/hooks_constants/pr_description_writer_gate_constants.py +39 -0
  75. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +31 -14
  76. package/hooks/hooks_constants/session_edit_stage_gate_constants.py +15 -1
  77. package/hooks/hooks_constants/state_description_blocker_constants.py +1 -0
  78. package/hooks/hooks_constants/subprocess_budget_completeness_content.py +71 -0
  79. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +2 -0
  80. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +2 -0
  81. package/hooks/hooks_constants/test_multi_edit_reconstruction.py +102 -0
  82. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +139 -25
  83. package/hooks/hooks_constants/test_subprocess_budget_completeness_content.py +70 -0
  84. package/hooks/hooks_constants/validation_phase_constants.py +2 -0
  85. package/hooks/hooks_constants/workflow_substitution_slot_blocker_constants.py +7 -3
  86. package/hooks/observability/pr_description_writer_spawn_tracker.py +121 -0
  87. package/hooks/observability/session_file_edit_tracker.py +25 -4
  88. package/hooks/observability/test_pr_description_writer_spawn_tracker.py +146 -0
  89. package/hooks/observability/test_session_file_edit_tracker.py +32 -0
  90. package/hooks/validation/hook_format_validator.py +23 -9
  91. package/hooks/validation/test_hook_format_validator.py +58 -0
  92. package/hooks/validators/README.md +1 -1
  93. package/hooks/validators/abbreviation_checks.py +3 -5
  94. package/hooks/validators/config/abbreviation_checks_constants.py +3 -0
  95. package/hooks/validators/config/fast_save_validator_constants.py +32 -0
  96. package/hooks/validators/config/hook_timing_harness_constants.py +3 -1
  97. package/hooks/validators/config/test_abbreviation_checks_constants.py +11 -0
  98. package/hooks/validators/fast_save_validators.py +332 -0
  99. package/hooks/validators/hook_timing_harness.py +8 -4
  100. package/hooks/validators/python_style_checks.py +34 -60
  101. package/hooks/validators/python_style_helpers.py +16 -2
  102. package/hooks/validators/python_style_import_bootstrap.py +83 -0
  103. package/hooks/validators/run_all_validators.py +42 -41
  104. package/hooks/validators/test_abbreviation_checks.py +20 -0
  105. package/hooks/validators/test_fast_save_validators.py +172 -0
  106. package/hooks/validators/test_python_style_checks.py +9 -0
  107. package/hooks/validators/test_python_style_checks_import_order.py +81 -0
  108. package/hooks/validators/test_python_style_helpers.py +35 -0
  109. package/hooks/validators/test_python_style_import_bootstrap.py +69 -0
  110. package/hooks/validators/test_run_all_validators.py +46 -0
  111. package/hooks/validators/test_run_all_validators_pretooluse.py +2 -0
  112. package/package.json +1 -1
  113. package/rules/docstring-prose-matches-implementation.md +1 -1
  114. package/rules/git-workflow.md +2 -0
  115. package/rules/testing.md +4 -0
  116. package/scripts/codex_compat_materializer.py +5 -0
  117. package/scripts/tests/test_codex_compat_materializer.py +37 -11
  118. package/hooks/blocking/tests/conftest.py +0 -10
@@ -64,6 +64,8 @@ codex_hook_dependency_manifest = (
64
64
  "hooks/blocking/code_rules_comments.py",
65
65
  "hooks/blocking/code_rules_constants_config.py",
66
66
  "hooks/blocking/codex_apply_patch.py",
67
+ "hooks/blocking/config/__init__.py",
68
+ "hooks/blocking/config/codex_apply_patch_constants.py",
67
69
  "hooks/blocking/code_rules_docstrings.py",
68
70
  "hooks/blocking/code_rules_duplicate_body.py",
69
71
  "hooks/blocking/code_rules_enforcer.py",
@@ -101,14 +103,17 @@ codex_hook_dependency_manifest = (
101
103
  "hooks/hooks_constants/hook_block_logger.py",
102
104
  "hooks/hooks_constants/inline_tuple_string_magic_constants.py",
103
105
  "hooks/hooks_constants/js_conventions_constants.py",
106
+ "hooks/hooks_constants/multi_edit_reconstruction.py",
104
107
  "hooks/hooks_constants/orphan_css_class_constants.py",
105
108
  "hooks/hooks_constants/paired_test_coverage_constants.py",
106
109
  "hooks/hooks_constants/setup_project_paths_constants.py",
107
110
  "hooks/hooks_constants/stuttering_check_config.py",
108
111
  "hooks/hooks_constants/stuttering_import_binding_constants.py",
112
+ "hooks/hooks_constants/subprocess_budget_completeness_content.py",
109
113
  "hooks/hooks_constants/sys_path_insert_constants.py",
110
114
  "hooks/hooks_constants/test_layout_constants.py",
111
115
  "hooks/hooks_constants/unused_module_import_constants.py",
116
+ "hooks/hooks_constants/validation_phase_constants.py",
112
117
  )
113
118
  full_prune_opt_in_flag = "--allow-prune-all"
114
119
  unreadable_source_root_message = (
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import os
2
3
  import subprocess
3
4
  import sys
4
5
  from pathlib import Path
@@ -841,23 +842,35 @@ def test_build_plan_publishes_owned_agents_projection_and_tracks_drift(
841
842
  assert drifted_projection != first_projection
842
843
 
843
844
 
844
- def test_codex_hook_manifest_keeps_dispatcher_order_and_adds_enforcer() -> None:
845
+ def test_codex_hook_manifest_folds_apply_patch_into_the_dispatcher_matcher() -> None:
846
+ """apply_patch's code_rules_enforcer coverage comes from the dispatcher's own matcher.
847
+
848
+ hooks.json once ran a separate apply_patch -> code_rules_enforcer.py
849
+ PreToolUse command after the dispatcher entry. That coverage is now
850
+ folded into the dispatcher's own Write|Edit|MultiEdit|apply_patch
851
+ matcher, so no standalone apply_patch entry remains.
852
+ """
845
853
  hooks_path = Path(__file__).parents[2] / "hooks" / "hooks.json"
846
854
  hooks_configuration = json.loads(hooks_path.read_text(encoding="utf-8"))
847
855
  pre_tool_use_entries = hooks_configuration["hooks"]["PreToolUse"]
848
- matcher_names = [each_entry.get("matcher") for each_entry in pre_tool_use_entries]
849
856
 
850
- dispatcher_index = matcher_names.index("Write|Edit|MultiEdit")
851
- enforcer_entries = [
857
+ dispatcher_entries = [
858
+ each_entry
859
+ for each_entry in pre_tool_use_entries
860
+ if any(
861
+ "/hooks/blocking/pre_tool_use_dispatcher.py" in each_hook.get("command", "")
862
+ for each_hook in each_entry.get("hooks", [])
863
+ )
864
+ ]
865
+ assert len(dispatcher_entries) == 1
866
+ assert dispatcher_entries[0]["matcher"] == "Write|Edit|MultiEdit|apply_patch"
867
+
868
+ standalone_apply_patch_entries = [
852
869
  each_entry
853
870
  for each_entry in pre_tool_use_entries
854
871
  if each_entry.get("matcher") == "apply_patch"
855
872
  ]
856
- assert len(enforcer_entries) == 1
857
- assert matcher_names.index("apply_patch") > dispatcher_index
858
- assert enforcer_entries[0]["hooks"][0]["command"].endswith(
859
- "/hooks/blocking/code_rules_enforcer.py"
860
- )
873
+ assert standalone_apply_patch_entries == []
861
874
 
862
875
 
863
876
  def _prepare_projection_fixture(
@@ -1008,7 +1021,18 @@ def _assert_projection_and_manifest(
1008
1021
 
1009
1022
 
1010
1023
  def _assert_detached_enforcer_behavior(target: Path) -> None:
1024
+ """Prove the detached, materialized enforcer allows a clean add and denies an unnamed raise.
1025
+
1026
+ Disables the ephemeral-path exemption for both runs: without it, a target
1027
+ rooted under the OS temp directory (where this fixture lives) would skip
1028
+ code_rules_enforcer's checks entirely and both assertions would pass
1029
+ without the enforcer ever judging the payload.
1030
+ """
1011
1031
  target_entrypoint = target / materializer.codex_enforcer_script_relative_path
1032
+ subprocess_environment = {
1033
+ **os.environ,
1034
+ "CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT": "1",
1035
+ }
1012
1036
  allow_payload = {
1013
1037
  "tool_name": "apply_patch",
1014
1038
  "cwd": str(target),
@@ -1029,8 +1053,9 @@ def _assert_detached_enforcer_behavior(target: Path) -> None:
1029
1053
  capture_output=True,
1030
1054
  text=True,
1031
1055
  check=False,
1056
+ env=subprocess_environment,
1032
1057
  )
1033
- assert allow_run.returncode == 0
1058
+ assert allow_run.returncode == 0, allow_run.stderr
1034
1059
  assert allow_run.stdout == ""
1035
1060
 
1036
1061
  allow_tool_input = allow_payload["tool_input"]
@@ -1050,8 +1075,9 @@ def _assert_detached_enforcer_behavior(target: Path) -> None:
1050
1075
  capture_output=True,
1051
1076
  text=True,
1052
1077
  check=False,
1078
+ env=subprocess_environment,
1053
1079
  )
1054
- assert deny_run.returncode == 0
1080
+ assert deny_run.returncode == 0, deny_run.stderr
1055
1081
  assert json.loads(deny_run.stdout)["hookSpecificOutput"]["permissionDecision"] == "deny"
1056
1082
  assert target_entrypoint.is_file()
1057
1083
 
@@ -1,10 +0,0 @@
1
- """Put the blocking and hooks directories on sys.path for the blocking tests."""
2
-
3
- import sys
4
- from pathlib import Path
5
-
6
- _blocking_directory = str(Path(__file__).resolve().parents[1])
7
- _hooks_directory = str(Path(__file__).resolve().parents[2])
8
- for each_bootstrap_directory in (_blocking_directory, _hooks_directory):
9
- if each_bootstrap_directory not in sys.path:
10
- sys.path.insert(0, each_bootstrap_directory)