claude-dev-env 2.29.0 → 2.30.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 (48) hide show
  1. package/.agents/agents/pr-description-writer.md +21 -6
  2. package/.agents/agents/reference/README.md +6 -0
  3. package/.agents/agents/reference/pr-description-illustrative-voice.md +45 -0
  4. package/.agents/agents/reference/pr-description-verification.md +19 -0
  5. package/.agents/agents/test_pr_description_writer_style.py +47 -0
  6. package/_shared/pr-loop/code-rules-gate.md +1 -1
  7. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -2
  8. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +8 -8
  9. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +2 -2
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +1 -1
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +15 -15
  12. package/docs/CODE_RULES.md +10 -0
  13. package/hooks/blocking/code_rules_enforcer.py +433 -313
  14. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +1 -1
  15. package/hooks/blocking/test_code_rules_enforcer_class_docstring_methods.py +1 -1
  16. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +117 -22
  17. package/hooks/blocking/test_code_rules_enforcer_docstring_args_signature.py +1 -1
  18. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +1 -1
  19. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +1 -1
  20. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +1 -1
  21. package/hooks/blocking/test_code_rules_enforcer_fstring_scan.py +1 -1
  22. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +1 -1
  23. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +1 -1
  24. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +2 -2
  25. package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +3 -3
  26. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +1 -1
  27. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +1 -1
  28. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +117 -38
  29. package/hooks/blocking/test_code_rules_enforcer_output_snapshot.py +121 -0
  30. package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +17 -15
  31. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +4 -2
  32. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +1 -1
  33. package/hooks/blocking/test_code_rules_enforcer_stage.py +1 -1
  34. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +4 -4
  35. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +2 -2
  36. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +2 -2
  37. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +2 -2
  38. package/hooks/blocking/test_code_rules_validation_phase.py +281 -0
  39. package/hooks/blocking/test_fable_spawn_gate.py +1 -1
  40. package/hooks/blocking/test_pre_tool_use_dispatcher.py +26 -11
  41. package/hooks/hooks_constants/code_rules_enforcer_constants.py +8 -0
  42. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +21 -0
  43. package/hooks/hooks_constants/validation_phase_constants.py +34 -0
  44. package/hooks/validators/README.md +1 -1
  45. package/hooks/validators/config/hook_timing_harness_constants.py +16 -0
  46. package/hooks/validators/hook_timing_harness.py +287 -0
  47. package/hooks/validators/test_hook_timing_harness.py +251 -0
  48. package/package.json +1 -1
@@ -7,6 +7,8 @@ import subprocess
7
7
  import sys
8
8
  from pathlib import Path
9
9
 
10
+ import pytest
11
+
10
12
  _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
11
13
  _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
12
14
  if _BLOCKING_DIRECTORY not in sys.path:
@@ -16,7 +18,7 @@ if _HOOKS_DIRECTORY not in sys.path:
16
18
 
17
19
  from code_rules_enforcer import ( # noqa: E402
18
20
  main,
19
- validate_content,
21
+ validate_content_for_full_gate as validate_content,
20
22
  )
21
23
  from code_rules_enforcer_test_support import ( # noqa: E402
22
24
  run_enforcer_cli,
@@ -115,7 +117,7 @@ def test_precheck_stream_parameter_carries_no_banned_noun() -> None:
115
117
 
116
118
 
117
119
  def test_precheck_reports_violations_and_exits_nonzero_for_production_candidate(
118
- tmp_path_factory: object,
120
+ tmp_path_factory: pytest.TempPathFactory,
119
121
  ) -> None:
120
122
  """A violating candidate judged at a production target prints each violation to
121
123
  stdout and exits 1."""
@@ -138,7 +140,7 @@ def test_precheck_reports_violations_and_exits_nonzero_for_production_candidate(
138
140
 
139
141
 
140
142
  def test_precheck_emits_no_output_and_exits_zero_for_clean_candidate(
141
- tmp_path_factory: object,
143
+ tmp_path_factory: pytest.TempPathFactory,
142
144
  ) -> None:
143
145
  """A clean candidate produces no stdout and exits 0."""
144
146
  staging_directory = getattr(tmp_path_factory, "mktemp")("staging")
@@ -155,7 +157,7 @@ def test_precheck_emits_no_output_and_exits_zero_for_clean_candidate(
155
157
 
156
158
 
157
159
  def test_precheck_target_path_drives_test_file_exemptions(
158
- tmp_path_factory: object,
160
+ tmp_path_factory: pytest.TempPathFactory,
159
161
  ) -> None:
160
162
  """Production-shaped content judged against a ``test_*.py`` target passes
161
163
  because test-file exemptions apply through the target path."""
@@ -172,7 +174,7 @@ def test_precheck_target_path_drives_test_file_exemptions(
172
174
 
173
175
 
174
176
  def test_precheck_noncode_target_exits_zero_with_no_output(
175
- tmp_path_factory: object,
177
+ tmp_path_factory: pytest.TempPathFactory,
176
178
  ) -> None:
177
179
  """A non-code target extension is exempt: the pre-check exits 0 with no output."""
178
180
  staging_directory = getattr(tmp_path_factory, "mktemp")("staging")
@@ -186,7 +188,7 @@ def test_precheck_noncode_target_exits_zero_with_no_output(
186
188
 
187
189
 
188
190
  def test_precheck_missing_candidate_errors_on_stderr_without_traceback(
189
- tmp_path_factory: object,
191
+ tmp_path_factory: pytest.TempPathFactory,
190
192
  ) -> None:
191
193
  """A nonexistent candidate path prints a one-line stderr error and exits
192
194
  nonzero without a Python traceback."""
@@ -204,7 +206,7 @@ def test_precheck_missing_candidate_errors_on_stderr_without_traceback(
204
206
 
205
207
 
206
208
  def test_edit_deny_reason_includes_forecast_and_precheck_hint(
207
- tmp_path_factory: object,
209
+ tmp_path_factory: pytest.TempPathFactory,
208
210
  ) -> None:
209
211
  """An Edit whose new_string introduces a violation on a file that already
210
212
  contains a separate violation elsewhere blocks on the fragment violation and
@@ -236,7 +238,7 @@ def test_edit_deny_reason_includes_forecast_and_precheck_hint(
236
238
 
237
239
 
238
240
  def test_edit_clean_fragment_on_dirty_file_produces_no_deny_payload(
239
- tmp_path_factory: object,
241
+ tmp_path_factory: pytest.TempPathFactory,
240
242
  ) -> None:
241
243
  """A clean Edit fragment on a file that is dirty elsewhere must not block —
242
244
  the forecast never converts a clean-fragment edit into a deny."""
@@ -258,7 +260,7 @@ def test_edit_clean_fragment_on_dirty_file_produces_no_deny_payload(
258
260
 
259
261
 
260
262
  def test_every_deny_reason_carries_the_precheck_hint(
261
- tmp_path_factory: object,
263
+ tmp_path_factory: pytest.TempPathFactory,
262
264
  ) -> None:
263
265
  """A deny with no forecast still appends the pre-check hint to the reason."""
264
266
  production_directory = getattr(tmp_path_factory, "mktemp")("production_pkg")
@@ -287,7 +289,7 @@ def test_every_deny_reason_carries_the_precheck_hint(
287
289
 
288
290
 
289
291
  def test_forecast_skipped_when_edit_prior_is_unreconstructable(
290
- tmp_path_factory: object,
292
+ tmp_path_factory: pytest.TempPathFactory,
291
293
  ) -> None:
292
294
  """An Edit whose old_string is absent from the file has no reliable prior to
293
295
  diff against, so the full-file forecast must not run: a pre-existing inline
@@ -313,7 +315,7 @@ def test_forecast_skipped_when_edit_prior_is_unreconstructable(
313
315
 
314
316
 
315
317
  def test_forecast_omits_the_fragment_introduced_violation(
316
- tmp_path_factory: object,
318
+ tmp_path_factory: pytest.TempPathFactory,
317
319
  ) -> None:
318
320
  """An Edit that introduces the file's only print() blocks on it without the
319
321
  forecast re-listing that same violation under its full-file line number."""
@@ -353,7 +355,7 @@ def test_precheck_missing_candidate_value_exits_two_with_usage() -> None:
353
355
 
354
356
 
355
357
  def test_precheck_flag_shaped_candidate_value_exits_two_with_usage(
356
- tmp_path_factory: object,
358
+ tmp_path_factory: pytest.TempPathFactory,
357
359
  ) -> None:
358
360
  """``--check`` immediately followed by ``--as`` is a usage error rather than
359
361
  an attempt to read a file literally named ``--as``."""
@@ -370,7 +372,7 @@ def test_precheck_flag_shaped_candidate_value_exits_two_with_usage(
370
372
 
371
373
 
372
374
  def test_precheck_rejects_unrecognized_trailing_token_with_usage(
373
- tmp_path_factory: object,
375
+ tmp_path_factory: pytest.TempPathFactory,
374
376
  ) -> None:
375
377
  """A pre-check vector carrying a token beyond the supported
376
378
  ``--check <candidate> [--as <target>]`` shape is a usage error: an extra
@@ -393,7 +395,7 @@ def test_precheck_rejects_unrecognized_trailing_token_with_usage(
393
395
 
394
396
 
395
397
  def test_precheck_strips_candidate_byte_order_mark_before_validation(
396
- tmp_path_factory: object,
398
+ tmp_path_factory: pytest.TempPathFactory,
397
399
  ) -> None:
398
400
  """A UTF-8 byte-order mark on the candidate must not hide AST-based
399
401
  violations: the candidate is judged exactly as its decoded content would
@@ -415,7 +417,7 @@ def test_precheck_strips_candidate_byte_order_mark_before_validation(
415
417
 
416
418
 
417
419
  def test_precheck_strips_every_leading_byte_order_mark_before_validation(
418
- tmp_path_factory: object,
420
+ tmp_path_factory: pytest.TempPathFactory,
419
421
  ) -> None:
420
422
  """Stacked byte-order marks must not hide AST-based violations: every
421
423
  leading mark is stripped, so a double-BOM candidate fails exactly like its
@@ -6,6 +6,8 @@ import sys
6
6
  from pathlib import Path
7
7
  from types import SimpleNamespace
8
8
 
9
+ import pytest
10
+
9
11
  _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
10
12
  _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
11
13
  if _BLOCKING_DIRECTORY not in sys.path:
@@ -17,7 +19,7 @@ from code_rules_annotations_length import ( # noqa: E402
17
19
  FUNCTION_LENGTH_BLOCKING_THRESHOLD,
18
20
  )
19
21
  from code_rules_enforcer import ( # noqa: E402
20
- validate_content,
22
+ validate_content_for_full_gate as validate_content,
21
23
  )
22
24
 
23
25
  code_rules_enforcer = SimpleNamespace(
@@ -38,7 +40,7 @@ def _oversized_function_source(name: str) -> str:
38
40
 
39
41
 
40
42
  def test_should_emit_advisory_for_duplicated_format_patterns_via_validate_content(
41
- capsys: object,
43
+ capsys: pytest.CaptureFixture[str],
42
44
  ) -> None:
43
45
  repeated_pattern_source = (
44
46
  "def get_user(user_id: str) -> str:\n"
@@ -22,7 +22,7 @@ from code_rules_annotations_length import ( # noqa: E402
22
22
  from code_rules_enforcer import ( # noqa: E402
23
23
  main,
24
24
  prior_and_post_edit_content,
25
- validate_content,
25
+ validate_content_for_full_gate as validate_content,
26
26
  )
27
27
  from code_rules_enforcer_test_support import ( # noqa: E402
28
28
  run_serialized_payload_entrypoint,
@@ -25,7 +25,7 @@ if _PR_LOOP_SCRIPTS_DIRECTORY not in sys.path:
25
25
  from code_rules_enforcer import ( # noqa: E402
26
26
  _fragment_or_deferred_check,
27
27
  main,
28
- validate_content,
28
+ validate_content_for_full_gate as validate_content,
29
29
  )
30
30
  from code_rules_gate_parts import gate_running # noqa: E402
31
31
  from code_rules_gate_parts.tests._repo_test_helpers import ( # noqa: E402
@@ -248,7 +248,7 @@ def test_should_grandfather_unchanged_string_magic_in_wide_edit() -> None:
248
248
  " # wide edit that leaves the magic string in place\n"
249
249
  " return os.environ['STRIPE_SECRET']\n"
250
250
  )
251
- issues = code_rules_enforcer.validate_content(
251
+ issues = code_rules_enforcer.validate_content_for_full_gate(
252
252
  proposed_fragment,
253
253
  PRODUCTION_FILE_PATH,
254
254
  prior_fragment,
@@ -272,7 +272,7 @@ def test_should_block_new_string_magic_in_wide_edit() -> None:
272
272
  "def fetch_secret() -> str:\n"
273
273
  " return os.environ['STRIPE_SECRET']\n"
274
274
  )
275
- issues = code_rules_enforcer.validate_content(
275
+ issues = code_rules_enforcer.validate_content_for_full_gate(
276
276
  proposed_fragment,
277
277
  PRODUCTION_FILE_PATH,
278
278
  prior_fragment,
@@ -300,7 +300,7 @@ def test_should_block_second_identical_string_magic_per_occurrence() -> None:
300
300
  "def fetch_backup() -> str:\n"
301
301
  " return os.environ['STRIPE_SECRET']\n"
302
302
  )
303
- issues = code_rules_enforcer.validate_content(
303
+ issues = code_rules_enforcer.validate_content_for_full_gate(
304
304
  proposed_fragment,
305
305
  PRODUCTION_FILE_PATH,
306
306
  prior_fragment,
@@ -320,7 +320,7 @@ def test_should_surface_magic_when_gate_defers_scope_with_head_as_old() -> None:
320
320
  " old_number = 9999\n"
321
321
  " return old_number\n"
322
322
  )
323
- issues = code_rules_enforcer.validate_content(
323
+ issues = code_rules_enforcer.validate_content_for_full_gate(
324
324
  source,
325
325
  PRODUCTION_FILE_PATH,
326
326
  source,
@@ -149,7 +149,7 @@ def test_should_flag_thin_wrapper_with_module_docstring() -> None:
149
149
  )
150
150
 
151
151
 
152
- def test_validate_content_uses_empty_full_file_content_over_pre_edit_fragment() -> None:
152
+ def test_validate_content_for_full_gate_uses_empty_full_file_content_over_pre_edit_fragment() -> None:
153
153
  """An empty-string `full_file_content` must be honored, not silently replaced with `content`.
154
154
 
155
155
  Regression for loop1-8: the `or` short-circuit at line 3775 collapsed
@@ -159,7 +159,7 @@ def test_validate_content_uses_empty_full_file_content_over_pre_edit_fragment()
159
159
  not a thin wrapper, but a pre-edit fragment with imports + __all__ is.
160
160
  """
161
161
  pre_edit_fragment = "from real_module import do_thing\n__all__ = ['do_thing']\n"
162
- issues = code_rules_enforcer.validate_content(
162
+ issues = code_rules_enforcer.validate_content_for_full_gate(
163
163
  pre_edit_fragment,
164
164
  PRODUCTION_FILE_PATH,
165
165
  full_file_content="",
@@ -240,7 +240,7 @@ def test_should_not_flag_recursive_self_call() -> None:
240
240
  )
241
241
 
242
242
 
243
- def test_should_flag_through_validate_content_for_hook_file() -> None:
243
+ def test_should_flag_through_validate_content_for_full_gate_for_hook_file() -> None:
244
244
  source = (
245
245
  "def find_bare_path_segments(content: str) -> set[str]:\n"
246
246
  " return {part for part in content.split() if part}\n"
@@ -248,7 +248,7 @@ def test_should_flag_through_validate_content_for_hook_file() -> None:
248
248
  "def find_bare_index_segments(content: str) -> set[str]:\n"
249
249
  " return find_bare_path_segments(content)\n"
250
250
  )
251
- issues = code_rules_enforcer.validate_content(source, HOOK_INFRASTRUCTURE_PATH)
251
+ issues = code_rules_enforcer.validate_content_for_full_gate(source, HOOK_INFRASTRUCTURE_PATH)
252
252
  assert any("find_bare_index_segments" in each for each in issues), (
253
253
  f"validate_content must surface the alias for hook files, got: {issues!r}"
254
254
  )
@@ -3,7 +3,7 @@
3
3
  A pass-through alias inside a hook module is the motivating case for the
4
4
  zero-payload-alias check, so the deny must fire on the same PreToolUse path a
5
5
  live Write into ``packages/claude-dev-env/hooks/blocking`` would take — not only
6
- through ``validate_content``, which hook files never reach at PreToolUse. These
6
+ through ``validate_content_for_edit_lane``, which hook files never reach at PreToolUse. These
7
7
  tests drive the real ``main()`` stdin entry point and the pre-check CLI with a
8
8
  hook-infrastructure target.
9
9
 
@@ -66,7 +66,7 @@ def test_write_of_pass_through_alias_into_hook_directory_denies(
66
66
  The target lives under a hook-infrastructure path the full code-rules suite
67
67
  exempts, so this proves the zero-payload-alias check still fires on the exact
68
68
  directory its docstring names as the motivating case — at the PreToolUse Write
69
- point, not only through ``validate_content``."""
69
+ point, not only through ``validate_content_for_edit_lane``."""
70
70
  new_file = hook_blocking_dir / "new_blocker.py"
71
71
  captured_stdout, _exit_code = run_write_entrypoint(
72
72
  main,
@@ -0,0 +1,281 @@
1
+ """Tests for the validate_content edit-lane and full-gate phase split."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import importlib
7
+ import importlib.util
8
+ import inspect
9
+ import re
10
+ import shutil
11
+ import sys
12
+ import tempfile
13
+ from pathlib import Path
14
+ from types import ModuleType
15
+ from unittest.mock import DEFAULT, MagicMock, patch
16
+
17
+ import pytest
18
+
19
+
20
+ def _load_module_from_path(module_name: str, module_path: Path) -> ModuleType:
21
+ """Load and execute the module found at module_path, under module_name."""
22
+ specification = importlib.util.spec_from_file_location(module_name, module_path)
23
+ assert specification is not None
24
+ assert specification.loader is not None
25
+ module = importlib.util.module_from_spec(specification)
26
+ specification.loader.exec_module(module)
27
+ return module
28
+
29
+
30
+ _BLOCKING_DIRECTORY = Path(__file__).resolve().parent
31
+ _HOOKS_DIRECTORY = _BLOCKING_DIRECTORY.parent
32
+ _PACKAGE_ROOT = _HOOKS_DIRECTORY.parent
33
+ _PR_LOOP_SCRIPTS_DIRECTORY = str(_PACKAGE_ROOT / "_shared" / "pr-loop" / "scripts")
34
+
35
+ code_rules_enforcer = _load_module_from_path(
36
+ "code_rules_enforcer", _BLOCKING_DIRECTORY / "code_rules_enforcer.py"
37
+ )
38
+ validation_phase_constants = _load_module_from_path(
39
+ "validation_phase_constants",
40
+ _HOOKS_DIRECTORY / "hooks_constants" / "validation_phase_constants.py",
41
+ )
42
+
43
+ if _PR_LOOP_SCRIPTS_DIRECTORY not in sys.path:
44
+ sys.path.insert(0, _PR_LOOP_SCRIPTS_DIRECTORY)
45
+ enforcer_loading = importlib.import_module("code_rules_gate_parts.enforcer_loading")
46
+ gate_running = importlib.import_module("code_rules_gate_parts.gate_running")
47
+ repo_test_helpers = importlib.import_module("code_rules_gate_parts.tests._repo_test_helpers")
48
+
49
+ PRODUCTION_FILE_PATH = "packages/app/services.py"
50
+ SAMPLE_PYTHON_CONTENT = "def add_one(value: int) -> int:\n return value + 1\n"
51
+ UNKNOWN_PHASE_NAME = "not_a_real_phase"
52
+ HOOK_INFRASTRUCTURE_FILE_PATH = "/repo/packages/claude-dev-env/hooks/blocking/example.py"
53
+ _HOOK_MODULE_DUPLICATE_BODY_SOURCE = (
54
+ "def compute_shipping_total(order_total: float, tax_rate: float) -> float:\n"
55
+ " taxed_total = order_total * (1 + tax_rate)\n"
56
+ " rounded_total = round(taxed_total, 2)\n"
57
+ " return rounded_total\n"
58
+ )
59
+
60
+
61
+ def test_all_validation_phases_holds_exactly_the_two_phases() -> None:
62
+ assert validation_phase_constants.ALL_VALIDATION_PHASES == {
63
+ validation_phase_constants.EDIT_LANE_PHASE,
64
+ validation_phase_constants.FULL_GATE_PHASE,
65
+ }
66
+
67
+
68
+ def test_unknown_phase_raises_value_error_naming_the_rejected_value() -> None:
69
+ with pytest.raises(ValueError, match=UNKNOWN_PHASE_NAME):
70
+ code_rules_enforcer.validate_content_for_phase(
71
+ SAMPLE_PYTHON_CONTENT, PRODUCTION_FILE_PATH, phase=UNKNOWN_PHASE_NAME
72
+ )
73
+
74
+
75
+ def test_validate_content_for_phase_requires_phase_keyword() -> None:
76
+ with pytest.raises(TypeError):
77
+ code_rules_enforcer.validate_content_for_phase(SAMPLE_PYTHON_CONTENT, PRODUCTION_FILE_PATH)
78
+
79
+
80
+ def _full_gate_only_default_kwargs() -> dict[str, object]:
81
+ return {
82
+ each_name: DEFAULT
83
+ for each_name in validation_phase_constants.ALL_FULL_GATE_ONLY_CHECK_NAMES
84
+ }
85
+
86
+
87
+ def _patched_full_gate_only_checks() -> contextlib.AbstractContextManager[dict[str, MagicMock]]:
88
+ """Return the patch.multiple context manager for the six full-gate-only checks.
89
+
90
+ ::
91
+
92
+ mypy's patch.multiple stub only accepts an ``Any | str`` target, so a
93
+ dynamically loaded ``ModuleType`` target needs a local type: ignore.
94
+ """
95
+ return patch.multiple( # type: ignore[call-overload] # mypy stub rejects ModuleType targets
96
+ code_rules_enforcer, **_full_gate_only_default_kwargs()
97
+ )
98
+
99
+
100
+ def _configure_full_gate_only_mock_returns(all_mocks: dict[str, MagicMock]) -> None:
101
+ for each_name, each_mock in all_mocks.items():
102
+ each_mock.return_value = None if each_name == "advise_cross_skill_duplicate_helper" else []
103
+
104
+
105
+ def test_edit_lane_calls_none_of_the_six_full_gate_only_checks() -> None:
106
+ with _patched_full_gate_only_checks() as all_mocks:
107
+ _configure_full_gate_only_mock_returns(all_mocks)
108
+ code_rules_enforcer.validate_content_for_edit_lane(
109
+ SAMPLE_PYTHON_CONTENT, PRODUCTION_FILE_PATH
110
+ )
111
+ for each_name, each_mock in all_mocks.items():
112
+ assert each_mock.call_count == 0, f"edit lane must not call {each_name}"
113
+
114
+
115
+ def test_full_gate_calls_all_six_full_gate_only_checks() -> None:
116
+ with _patched_full_gate_only_checks() as all_mocks:
117
+ _configure_full_gate_only_mock_returns(all_mocks)
118
+ code_rules_enforcer.validate_content_for_full_gate(
119
+ SAMPLE_PYTHON_CONTENT, PRODUCTION_FILE_PATH
120
+ )
121
+ for each_name, each_mock in all_mocks.items():
122
+ assert each_mock.call_count >= 1, f"full gate must call {each_name}"
123
+
124
+
125
+ def test_both_phases_report_identical_findings_with_the_six_spied_out() -> None:
126
+ with _patched_full_gate_only_checks() as all_mocks:
127
+ _configure_full_gate_only_mock_returns(all_mocks)
128
+ edit_lane_issues = code_rules_enforcer.validate_content_for_edit_lane(
129
+ SAMPLE_PYTHON_CONTENT, PRODUCTION_FILE_PATH
130
+ )
131
+ full_gate_issues = code_rules_enforcer.validate_content_for_full_gate(
132
+ SAMPLE_PYTHON_CONTENT, PRODUCTION_FILE_PATH
133
+ )
134
+ assert edit_lane_issues == full_gate_issues
135
+
136
+
137
+ def test_no_caller_should_reach_a_phase_less_validator() -> None:
138
+ assert not hasattr(code_rules_enforcer, "validate_content"), (
139
+ "validate_content must not survive as a callable on the enforcer module"
140
+ )
141
+ enforcer_source = inspect.getsource(code_rules_enforcer)
142
+ assert re.search(r"\bvalidate_content\(", enforcer_source) is None, (
143
+ "no call site in code_rules_enforcer.py may reach a phase-less validator"
144
+ )
145
+
146
+
147
+ def test_precheck_selects_the_full_gate() -> None:
148
+ precheck_source = inspect.getsource(code_rules_enforcer._run_precheck)
149
+ assert "validate_content_for_full_gate(" in precheck_source, (
150
+ "the --check precheck must run the full-gate verdict"
151
+ )
152
+
153
+
154
+ def test_codex_patch_issues_selects_the_edit_lane() -> None:
155
+ """Pin the apply_patch mutation path to the edit-lane verdict."""
156
+ codex_patch_issues_source = inspect.getsource(code_rules_enforcer._codex_patch_issues)
157
+ assert "validate_content_for_edit_lane(" in codex_patch_issues_source, (
158
+ "the apply_patch branch must run the edit-lane verdict"
159
+ )
160
+
161
+
162
+ def test_forecast_full_file_violations_selects_the_edit_lane() -> None:
163
+ """Pin the full-file forecast pass to the edit-lane verdict."""
164
+ forecast_source = inspect.getsource(code_rules_enforcer._forecast_full_file_violations)
165
+ assert "validate_content_for_edit_lane(" in forecast_source, (
166
+ "the full-file forecast pass must run the edit-lane verdict"
167
+ )
168
+
169
+
170
+ def test_report_blocking_violations_selects_the_edit_lane() -> None:
171
+ """Pin the PreToolUse Write and Edit main path to the edit-lane verdict."""
172
+ report_blocking_violations_source = inspect.getsource(
173
+ code_rules_enforcer._report_blocking_violations
174
+ )
175
+ assert "validate_content_for_edit_lane(" in report_blocking_violations_source, (
176
+ "the PreToolUse Write and Edit main path must run the edit-lane verdict"
177
+ )
178
+
179
+
180
+ def _write_scoped_service_file_with_a_committed_baseline(repository_root: Path) -> Path:
181
+ repo_test_helpers.init_repository(repository_root)
182
+ return repo_test_helpers.write_commit_and_stage_change(
183
+ repository_root,
184
+ "service.py",
185
+ "def read_port() -> int:\n return 0\n",
186
+ "def read_port() -> int:\n port_number = 9999\n return port_number\n",
187
+ )
188
+
189
+
190
+ def test_hook_infrastructure_edit_lane_skips_the_cross_file_duplicate_check() -> None:
191
+ with patch.object(
192
+ code_rules_enforcer, "check_duplicate_function_body_across_files"
193
+ ) as mock_check_duplicate_across_files:
194
+ mock_check_duplicate_across_files.return_value = []
195
+ code_rules_enforcer._hook_infrastructure_blocking_issues(
196
+ SAMPLE_PYTHON_CONTENT,
197
+ HOOK_INFRASTRUCTURE_FILE_PATH,
198
+ phase=validation_phase_constants.EDIT_LANE_PHASE,
199
+ )
200
+ assert mock_check_duplicate_across_files.call_count == 0, (
201
+ "the hook-infrastructure edit lane must not call the cross-file duplicate-body check"
202
+ )
203
+
204
+
205
+ def test_hook_infrastructure_full_gate_calls_the_cross_file_duplicate_check() -> None:
206
+ with patch.object(
207
+ code_rules_enforcer, "check_duplicate_function_body_across_files"
208
+ ) as mock_check_duplicate_across_files:
209
+ mock_check_duplicate_across_files.return_value = []
210
+ code_rules_enforcer._hook_infrastructure_blocking_issues(
211
+ SAMPLE_PYTHON_CONTENT,
212
+ HOOK_INFRASTRUCTURE_FILE_PATH,
213
+ phase=validation_phase_constants.FULL_GATE_PHASE,
214
+ )
215
+ assert mock_check_duplicate_across_files.call_count == 1, (
216
+ "the hook-infrastructure full gate must still call the cross-file duplicate-body check"
217
+ )
218
+
219
+
220
+ def _write_duplicate_helper_pair_under_a_non_test_named_tempdir() -> tuple[Path, str]:
221
+ """Write a hook-infra sibling pair under a ``tempfile.mkdtemp()`` root.
222
+
223
+ ``tempfile.mkdtemp()`` names its directory ``tmp<random>``, carrying no
224
+ ``test_`` segment, unlike pytest's own ``tmp_path`` fixture — whose
225
+ directory embeds the test's name and so reads as a test path to
226
+ ``is_test_file``'s full-path substring match, silently exempting every
227
+ check the caller means to exercise.
228
+ """
229
+ base_directory = Path(tempfile.mkdtemp())
230
+ hook_blocking_directory = base_directory / "packages" / "claude-dev-env" / "hooks" / "blocking"
231
+ hook_blocking_directory.mkdir(parents=True)
232
+ (hook_blocking_directory / "shipping_helper.py").write_text(
233
+ _HOOK_MODULE_DUPLICATE_BODY_SOURCE, encoding="utf-8"
234
+ )
235
+ return base_directory, str(hook_blocking_directory / "shipping_target.py")
236
+
237
+
238
+ def test_full_gate_still_catches_a_helper_duplicated_across_two_hook_modules() -> None:
239
+ base_directory, target_path = _write_duplicate_helper_pair_under_a_non_test_named_tempdir()
240
+ try:
241
+ full_gate_issues = code_rules_enforcer._hook_infrastructure_blocking_issues(
242
+ _HOOK_MODULE_DUPLICATE_BODY_SOURCE,
243
+ target_path,
244
+ phase=validation_phase_constants.FULL_GATE_PHASE,
245
+ )
246
+ edit_lane_issues = code_rules_enforcer._hook_infrastructure_blocking_issues(
247
+ _HOOK_MODULE_DUPLICATE_BODY_SOURCE,
248
+ target_path,
249
+ phase=validation_phase_constants.EDIT_LANE_PHASE,
250
+ )
251
+ finally:
252
+ shutil.rmtree(base_directory, ignore_errors=False)
253
+
254
+ assert any("duplicates" in each_issue for each_issue in full_gate_issues), (
255
+ "the full gate must still catch a helper duplicated across two hook "
256
+ f"modules, got: {full_gate_issues!r}"
257
+ )
258
+ assert not any("duplicates" in each_issue for each_issue in edit_lane_issues), (
259
+ "the edit lane must not run the cross-file duplicate-body check against "
260
+ f"hook infrastructure, got: {edit_lane_issues!r}"
261
+ )
262
+
263
+
264
+ def test_gate_running_selects_the_full_gate(tmp_path: Path) -> None:
265
+ repository_root = tmp_path / "repo"
266
+ repository_root.mkdir()
267
+ file_path = _write_scoped_service_file_with_a_committed_baseline(repository_root)
268
+
269
+ loaded_validate_content = enforcer_loading.load_validate_content_for_full_gate()
270
+ assert loaded_validate_content.__name__ == "validate_content_for_full_gate"
271
+
272
+ scoped_violations = gate_running._scoped_violations_for_file(
273
+ validate_content=loaded_validate_content,
274
+ resolved_path=file_path,
275
+ repository_root=repository_root,
276
+ all_added_lines_for_file={2, 3},
277
+ should_read_staged_content=True,
278
+ )
279
+ assert scoped_violations is not None
280
+ blocking_violations, _advisory_violations = scoped_violations
281
+ assert any("9999" in each_issue for each_issue in blocking_violations)
@@ -72,7 +72,7 @@ _ADVISOR_LIFECYCLE_TEXT = (_ADVISOR_REFERENCE_DIR / "lifecycle.md").read_text(
72
72
  _ADVISOR_WARM_UP_TEXT = (_ADVISOR_REFERENCE_DIR / "warm-up.md").read_text(
73
73
  encoding="utf-8"
74
74
  )
75
- _ALL_CONSUMING_SKILL_NAMES = ("team-advisor", "orchestrator", "orchestrator-refresh")
75
+ _ALL_CONSUMING_SKILL_NAMES = ("team-advisor",)
76
76
  _ALL_CONSUMING_SKILL_PATHS = tuple(
77
77
  _PACKAGE_ROOT / ".agents" / "skills" / each_skill_name / "SKILL.md"
78
78
  for each_skill_name in _ALL_CONSUMING_SKILL_NAMES
@@ -734,16 +734,31 @@ def test_dispatcher_multi_edit_applies_only_group_b() -> None:
734
734
  )
735
735
 
736
736
 
737
- def test_proceed_after_run_all_validators_removal_allows() -> None:
738
- """The PreToolUse dispatcher allows a Python edit that the removed gate would have processed.
739
-
740
- The inline run_all_validators runner was a PostToolUse gate removed in Stage 4;
741
- it was never a PreToolUse hook and never hosted by the PreToolUse dispatcher.
742
- A Python Write payload that run_all_validators would have flagged (mypy errors, for
743
- instance) still produces ALLOW from the PreToolUse dispatcher because the PreToolUse
744
- dispatcher covers only its 23 hosted blocking hooks — none of which includes the
745
- validators runner.
737
+ def _assert_roster_names_no_run_all_validators_entry() -> None:
738
+ """Assert no hosted-roster ``script_relative_path`` names run_all_validators."""
739
+ all_roster_script_paths = {
740
+ each_entry.script_relative_path for each_entry in ALL_HOSTED_HOOK_ENTRIES
741
+ }
742
+ assert not any(
743
+ "run_all_validators" in each_script_path
744
+ for each_script_path in all_roster_script_paths
745
+ ), (
746
+ "ALL_HOSTED_HOOK_ENTRIES must name no run_all_validators entry, got: "
747
+ f"{sorted(all_roster_script_paths)}"
748
+ )
749
+
750
+
751
+ def test_run_all_validators_is_not_hosted_by_pre_tool_use_dispatcher() -> None:
752
+ """The main PreToolUse dispatcher's hosted roster names no run_all_validators entry.
753
+
754
+ ``hooks.json`` registers the ``run_all_validators`` runner as its own
755
+ PreToolUse ``Write|Edit`` command, separate from the main dispatcher. A
756
+ Python Write payload that ``run_all_validators`` would flag (a type error,
757
+ for instance) still produces ALLOW from the main dispatcher, because its
758
+ roster carries no entry for that runner.
746
759
  """
760
+ _assert_roster_names_no_run_all_validators_entry()
761
+
747
762
  python_content_with_type_error = (
748
763
  "def add_one(value: int) -> int:\n"
749
764
  " return value + 1\n\n\n"
@@ -754,8 +769,8 @@ def test_proceed_after_run_all_validators_removal_allows() -> None:
754
769
  is_deny, _reason = _parse_hook_decision(dispatcher_result)
755
770
  assert not is_deny, (
756
771
  "PreToolUse dispatcher must allow a Python Write with a type error — "
757
- "mypy validation is PostToolUse-only; the removed run_all_validators gate "
758
- "was never a PreToolUse hook"
772
+ "run_all_validators runs as its own separate PreToolUse command, not "
773
+ "through this dispatcher's roster"
759
774
  )
760
775
  assert dispatcher_result.returncode == 0, (
761
776
  f"Dispatcher must exit 0, got {dispatcher_result.returncode}"
@@ -6,6 +6,14 @@ Extracted from code_rules_enforcer.py to satisfy the constants-location rule.
6
6
  import re
7
7
  import tokenize
8
8
 
9
+ from hooks_constants.validation_phase_constants import ( # noqa: F401 re-exported for code_rules_enforcer.py
10
+ ALL_HOOK_INFRASTRUCTURE_EDIT_LANE_CHECK_NAMES,
11
+ ALL_VALIDATION_PHASES,
12
+ EDIT_LANE_PHASE,
13
+ FULL_GATE_PHASE,
14
+ UNKNOWN_VALIDATION_PHASE_MESSAGE_TEMPLATE,
15
+ )
16
+
9
17
  ALL_PYTHON_TOKENIZE_FAILURE_EXCEPTIONS: tuple[type[BaseException], ...] = (
10
18
  tokenize.TokenError,
11
19
  IndentationError,