claude-dev-env 2.29.0 → 2.30.1

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 (60) 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/.agents/skills/e-simplify/SKILL.md +20 -8
  7. package/_shared/pr-loop/code-rules-gate.md +1 -1
  8. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -2
  9. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +8 -8
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +2 -2
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +1 -1
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +15 -15
  13. package/docs/CODE_RULES.md +10 -0
  14. package/hooks/blocking/code_rules_enforcer.py +435 -318
  15. package/hooks/blocking/shell_substitution_blocker.py +106 -30
  16. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +14 -0
  17. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +1 -1
  18. package/hooks/blocking/test_code_rules_enforcer_class_docstring_methods.py +1 -1
  19. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +117 -22
  20. package/hooks/blocking/test_code_rules_enforcer_docstring_args_signature.py +1 -1
  21. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +1 -1
  22. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +1 -1
  23. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +1 -1
  24. package/hooks/blocking/test_code_rules_enforcer_fstring_scan.py +1 -1
  25. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +1 -1
  26. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +1 -1
  27. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +2 -2
  28. package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +3 -3
  29. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +1 -1
  30. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +1 -1
  31. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +117 -38
  32. package/hooks/blocking/test_code_rules_enforcer_output_snapshot.py +121 -0
  33. package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +17 -15
  34. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +4 -2
  35. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +1 -1
  36. package/hooks/blocking/test_code_rules_enforcer_stage.py +1 -1
  37. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +4 -4
  38. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +2 -2
  39. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +2 -2
  40. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +2 -2
  41. package/hooks/blocking/test_code_rules_validation_phase.py +332 -0
  42. package/hooks/blocking/test_fable_spawn_gate.py +1 -1
  43. package/hooks/blocking/test_pre_tool_use_dispatcher.py +26 -11
  44. package/hooks/blocking/test_shell_substitution_blocker.py +69 -0
  45. package/hooks/hooks.json +1 -1
  46. package/hooks/hooks_constants/code_rules_enforcer_constants.py +7 -0
  47. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +4 -1
  48. package/hooks/hooks_constants/task_tool_prompt_constants.py +13 -0
  49. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +12 -0
  50. package/hooks/hooks_constants/validation_phase_constants.py +34 -0
  51. package/hooks/session/task_tool_prompt.py +37 -0
  52. package/hooks/session/test_task_tool_prompt.py +65 -0
  53. package/hooks/validators/README.md +1 -1
  54. package/hooks/validators/config/hook_timing_harness_constants.py +16 -0
  55. package/hooks/validators/hook_timing_harness.py +287 -0
  56. package/hooks/validators/test_hook_timing_harness.py +251 -0
  57. package/package.json +1 -1
  58. package/hooks/hooks_constants/task_list_loop_starter_constants.py +0 -19
  59. package/hooks/session/task_list_loop_starter.py +0 -45
  60. package/hooks/session/test_task_list_loop_starter.py +0 -63
@@ -24,13 +24,28 @@ are treated as literal only when the count of preceding backslashes is odd;
24
24
  an even count (including zero) means the backtick is a live substitution.
25
25
 
26
26
  Bash arithmetic expansion ``$((...))`` is explicitly NOT blocked: it does not
27
- spawn a subshell and does not defeat the allowlist matcher, so the
28
- ``$(`` pattern uses a negative lookahead to skip the ``$((`` case.
27
+ spawn a subshell and does not defeat the allowlist matcher. Telling it apart
28
+ from a disguised command substitution takes more than counting two open
29
+ parens, because bash itself falls back from arithmetic to command
30
+ substitution when the expansion body does not close as arithmetic. Observed
31
+ against real bash:
32
+
33
+ echo $((1 + 2)) prints 3 (arithmetic; not blocked)
34
+ echo $((cd /tmp) && pwd) prints the new cwd (command substitution; blocked)
35
+ echo $((1+2) ) runs "1+2" as a command, rc=0, no output
36
+ (command substitution; blocked)
37
+
38
+ Bash's own rule is the terminator: walk forward from ``$((`` tracking paren
39
+ depth; when depth returns to zero on a closer immediately preceded by
40
+ another ``)`` (an ``))`` pair), the expansion is arithmetic and passes. When
41
+ the closer that brings depth to zero is a lone ``)``, bash has fallen back to
42
+ running a parenthesized command inside the substitution, so it is blocked.
43
+ This hook applies that walk, tracking paren depth from the opening ``$((``.
29
44
 
30
45
  False positives are still possible when the user is intentionally writing
31
46
  shell-script content (for example, authoring a .sh file via heredoc) and the
32
47
  substitution is literal payload rather than something to execute. In that
33
- case, write via the Write tool instead of a Bash heredoc.
48
+ case, author the file with the Write tool, not a Bash heredoc.
34
49
  """
35
50
 
36
51
  from __future__ import annotations
@@ -39,34 +54,95 @@ import json
39
54
  import sys
40
55
  from pathlib import Path
41
56
 
42
- _hooks_dir = str(Path(__file__).resolve().parent.parent)
43
- if _hooks_dir not in sys.path:
44
- sys.path.insert(0, _hooks_dir)
45
-
46
- from hooks_constants.shell_substitution_blocker_constants import ( # noqa: E402
47
- BASH_TOOL_NAME,
48
- COMMAND_KEY,
49
- CORRECTIVE_MESSAGE,
50
- DENY_DECISION,
51
- DOLLAR_PAREN_PATTERN,
52
- EVEN_BACKSLASH_BACKTICK_PATTERN,
53
- HOOK_EVENT_NAME,
54
- HOOK_EVENT_NAME_KEY,
55
- HOOK_SPECIFIC_OUTPUT_KEY,
56
- PERMISSION_DECISION_KEY,
57
- PERMISSION_DECISION_REASON_KEY,
58
- PROCESS_SUBSTITUTION_PATTERN,
59
- SINGLE_QUOTED_RUN_PATTERN,
60
- STRIPPED_RUN_REPLACEMENT,
61
- TOOL_INPUT_KEY,
62
- TOOL_NAME_KEY,
63
- )
57
+ try:
58
+ from hooks_constants.shell_substitution_blocker_constants import (
59
+ BASH_TOOL_NAME,
60
+ CLOSE_PAREN_CHARACTER,
61
+ COMMAND_KEY,
62
+ CORRECTIVE_MESSAGE,
63
+ DENY_DECISION,
64
+ DOLLAR_PAREN_PATTERN,
65
+ EVEN_BACKSLASH_BACKTICK_PATTERN,
66
+ HOOK_EVENT_NAME,
67
+ HOOK_EVENT_NAME_KEY,
68
+ HOOK_SPECIFIC_OUTPUT_KEY,
69
+ OPEN_PAREN_CHARACTER,
70
+ PAREN_DEPTH_AFTER_DOUBLE_OPEN,
71
+ PERMISSION_DECISION_KEY,
72
+ PERMISSION_DECISION_REASON_KEY,
73
+ PROCESS_SUBSTITUTION_PATTERN,
74
+ SINGLE_QUOTED_RUN_PATTERN,
75
+ STRIPPED_RUN_REPLACEMENT,
76
+ TOOL_INPUT_KEY,
77
+ TOOL_NAME_KEY,
78
+ )
79
+ except ModuleNotFoundError:
80
+ _hooks_dir = str(Path(__file__).resolve().parent.parent)
81
+ if _hooks_dir not in sys.path:
82
+ sys.path.insert(0, _hooks_dir)
83
+ from hooks_constants.shell_substitution_blocker_constants import (
84
+ BASH_TOOL_NAME,
85
+ CLOSE_PAREN_CHARACTER,
86
+ COMMAND_KEY,
87
+ CORRECTIVE_MESSAGE,
88
+ DENY_DECISION,
89
+ DOLLAR_PAREN_PATTERN,
90
+ EVEN_BACKSLASH_BACKTICK_PATTERN,
91
+ HOOK_EVENT_NAME,
92
+ HOOK_EVENT_NAME_KEY,
93
+ HOOK_SPECIFIC_OUTPUT_KEY,
94
+ OPEN_PAREN_CHARACTER,
95
+ PAREN_DEPTH_AFTER_DOUBLE_OPEN,
96
+ PERMISSION_DECISION_KEY,
97
+ PERMISSION_DECISION_REASON_KEY,
98
+ PROCESS_SUBSTITUTION_PATTERN,
99
+ SINGLE_QUOTED_RUN_PATTERN,
100
+ STRIPPED_RUN_REPLACEMENT,
101
+ TOOL_INPUT_KEY,
102
+ TOOL_NAME_KEY,
103
+ )
64
104
 
65
105
 
66
106
  def _strip_single_quoted_runs(command: str) -> str:
67
107
  return SINGLE_QUOTED_RUN_PATTERN.sub(STRIPPED_RUN_REPLACEMENT, command)
68
108
 
69
109
 
110
+ def _closes_as_arithmetic_expansion(command: str, scan_start_index: int) -> bool:
111
+ """Return True when a ``$((`` expansion closes on an ``))`` pair.
112
+
113
+ See the module docstring for the real-bash evidence behind this rule.
114
+ """
115
+ paren_depth = PAREN_DEPTH_AFTER_DOUBLE_OPEN
116
+ was_previous_character_close_paren = False
117
+ for each_index in range(scan_start_index, len(command)):
118
+ current_character = command[each_index]
119
+ if current_character == OPEN_PAREN_CHARACTER:
120
+ paren_depth += 1
121
+ was_previous_character_close_paren = False
122
+ continue
123
+ if current_character != CLOSE_PAREN_CHARACTER:
124
+ was_previous_character_close_paren = False
125
+ continue
126
+ paren_depth -= 1
127
+ if paren_depth == 0:
128
+ return was_previous_character_close_paren
129
+ was_previous_character_close_paren = True
130
+ return False
131
+
132
+
133
+ def _has_unsafe_dollar_paren_expansion(command: str) -> bool:
134
+ """Return True for a `$(` that is not a safe `$((...))` arithmetic pair."""
135
+ for each_match in DOLLAR_PAREN_PATTERN.finditer(command):
136
+ is_arithmetic_style_open = (
137
+ command[each_match.end() : each_match.end() + 1] == OPEN_PAREN_CHARACTER
138
+ )
139
+ if not is_arithmetic_style_open:
140
+ return True
141
+ if not _closes_as_arithmetic_expansion(command, each_match.end() + 1):
142
+ return True
143
+ return False
144
+
145
+
70
146
  def has_shell_substitution(command: str) -> bool:
71
147
  """Return True when a Bash command carries a live shell substitution.
72
148
 
@@ -79,18 +155,18 @@ def has_shell_substitution(command: str) -> bool:
79
155
  echo $((2 + 2)) ok: arithmetic expansion
80
156
 
81
157
  Single-quoted runs are stripped first because bash substitutes nothing
82
- inside them. A backtick preceded by an odd number of backslashes is escaped
83
- and stays allowed.
158
+ inside them. A backtick preceded by an odd backslash count is escaped.
159
+ A `$((` pair is walked to its terminator (module docstring) to tell
160
+ real arithmetic from a disguised subshell.
84
161
 
85
162
  Args:
86
163
  command: The raw Bash command string from the tool input.
87
164
 
88
165
  Returns:
89
- True when a substitution the allowlist matcher cannot descend into is
90
- present outside single quotes.
166
+ True when a substitution the matcher cannot descend into is present.
91
167
  """
92
168
  scannable_command = _strip_single_quoted_runs(command)
93
- if DOLLAR_PAREN_PATTERN.search(scannable_command):
169
+ if _has_unsafe_dollar_paren_expansion(scannable_command):
94
170
  return True
95
171
  if EVEN_BACKSLASH_BACKTICK_PATTERN.search(scannable_command):
96
172
  return True
@@ -261,6 +261,20 @@ def test_emit_decision_re_emits_silent_deny_fields(
261
261
  assert emitted_payload["suppressOutput"] is True
262
262
 
263
263
 
264
+ def test_dispatcher_denies_subshell_disguised_as_arithmetic_expansion() -> None:
265
+ """The dispatcher denies a `$((cd X) && Y)` subshell, not only the bare hook.
266
+
267
+ `echo $((cd /tmp) && pwd)` opens with a `$((` two-character lookahead
268
+ match, so a function-level test alone does not prove the block reaches
269
+ the live Bash tool path.
270
+ """
271
+ payload_text = _bash_payload("echo $((cd /tmp) && pwd)")
272
+ decision, _reason = _decision_from_stdout(
273
+ _run_process(_DISPATCHER_SCRIPT, payload_text).stdout
274
+ )
275
+ assert decision == "deny"
276
+
277
+
264
278
  def test_dispatch_emits_deny_immediately_and_skips_later_hooks(
265
279
  monkeypatch: pytest.MonkeyPatch,
266
280
  capsys: pytest.CaptureFixture[str],
@@ -28,7 +28,7 @@ assert hook_spec.loader is not None
28
28
  hook_module = importlib.util.module_from_spec(hook_spec)
29
29
  hook_spec.loader.exec_module(hook_module)
30
30
  check_banned_noun_word_boundary = hook_module.check_banned_noun_word_boundary
31
- validate_content = hook_module.validate_content
31
+ validate_content = hook_module.validate_content_for_full_gate
32
32
 
33
33
  from code_rules_banned_identifiers import ( # noqa: E402
34
34
  _find_banned_noun_word,
@@ -31,7 +31,7 @@ def check_class_docstring_names_public_methods(content: str, file_path: str) ->
31
31
 
32
32
 
33
33
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
34
- return code_rules_enforcer.validate_content(content, file_path, old_content)
34
+ return code_rules_enforcer.validate_content_for_full_gate(content, file_path, old_content)
35
35
 
36
36
 
37
37
  PRODUCTION_FILE_PATH = "/project/src/break_reporter.py"
@@ -1,25 +1,49 @@
1
- """Meta-test asserting every check_* function is dispatched from validate_content.
1
+ """Meta-test asserting every check_* function is reachable from the full gate.
2
2
 
3
3
  The per-check test modules each prove one ``check_*`` function flags the right
4
4
  violation, but none proves the enforcer actually calls that function. A refactor
5
- that drops a dispatch line from ``validate_content`` leaves every per-check test
6
- green while the check stops firing at Write/Edit time — the precise failure mode
5
+ that drops a dispatch line from ``validate_content_for_full_gate`` or from one
6
+ of its extension-issue helper functions leaves every per-check test green
7
+ while the check stops firing at Write/Edit time — the precise failure mode
7
8
  that would let a dead module-level constant (the ``MEDIUM_TEXT`` class) or an
8
9
  orphan CSS class slip past the gate again.
9
10
 
10
- This module reads ``validate_content``'s source and asserts every ``check_*``
11
- attribute on the enforcer module appears in it. A check that is intentionally
12
- not wired must be listed in ``KNOWN_UNDISPATCHED_CHECKS`` with a reason in this
13
- docstring. ``check_unanchored_command_dispatch`` is listed there: it guards a
14
- ``hooks/blocking`` command classifier, and the whole ``validate_content`` verdict
15
- stays off hook-infrastructure files, so the enforcer dispatches it from
11
+ ::
12
+
13
+ validate_content_for_full_gate -> _python_extension_issues -> _python_magic_value_and_constant_issues
14
+ |
15
+ v
16
+ references check_magic_values
17
+ -> check_magic_values counts as reachable
18
+
19
+ check_unanchored_command_dispatch: called only from
20
+ _hook_infrastructure_blocking_issues, which the full gate's call graph
21
+ never reaches -> stays in KNOWN_UNDISPATCHED_CHECKS
22
+
23
+ This module walks the enforcer's real call graph from
24
+ ``validate_content_for_full_gate``, following every direct function call into
25
+ the functions the enforcer module defines, and collects every
26
+ ``check_*``/``advise_*`` name each reached function body references — a direct
27
+ call or a bare reference passed on as a callback (the
28
+ ``_fragment_or_deferred_check(check_magic_values, ...)`` shape). A name that
29
+ surfaces only in an import statement or a docstring is not a reference inside
30
+ a reached function body, so it does not count; this is what keeps the check
31
+ from going vacuous the way a whole-module substring scan would.
32
+
33
+ A check that is intentionally not wired must be listed in
34
+ ``KNOWN_UNDISPATCHED_CHECKS`` with a reason in this docstring.
35
+ ``check_unanchored_command_dispatch`` is listed there: it guards a
36
+ ``hooks/blocking`` command classifier, and the whole
37
+ ``validate_content_for_full_gate`` verdict stays off hook-infrastructure
38
+ files, so the enforcer dispatches it from
16
39
  ``_hook_infrastructure_blocking_issues`` instead. The companion
17
- ``test_code_rules_enforcer_cap_meta.py`` guards the payload-cap convention; this
18
- module guards the wiring.
40
+ ``test_code_rules_enforcer_cap_meta.py`` guards the payload-cap convention;
41
+ this module guards the wiring.
19
42
  """
20
43
 
21
44
  from __future__ import annotations
22
45
 
46
+ import ast
23
47
  import importlib.util
24
48
  import inspect
25
49
  import pathlib
@@ -42,6 +66,8 @@ KNOWN_UNDISPATCHED_CHECKS: frozenset[str] = frozenset(
42
66
  {"check_unanchored_command_dispatch"}
43
67
  )
44
68
 
69
+ DISPATCH_ENTRY_POINT_NAME = "validate_content_for_full_gate"
70
+
45
71
 
46
72
  def _all_check_function_names() -> list[str]:
47
73
  return [
@@ -52,22 +78,91 @@ def _all_check_function_names() -> list[str]:
52
78
  ]
53
79
 
54
80
 
55
- def _validate_content_source() -> str:
56
- return inspect.getsource(_hook_module.validate_content)
81
+ def _module_level_function_defs(module_tree: ast.Module) -> dict[str, ast.FunctionDef]:
82
+ """Map each module-level function's name to its definition node."""
83
+ return {
84
+ each_node.name: each_node
85
+ for each_node in module_tree.body
86
+ if isinstance(each_node, ast.FunctionDef)
87
+ }
57
88
 
58
89
 
59
- def test_every_check_function_is_called_in_validate_content() -> None:
60
- all_check_names = set(_all_check_function_names())
61
- validate_content_source = _validate_content_source()
62
- undispatched_check_names = {
63
- each_name for each_name in all_check_names if each_name not in validate_content_source
90
+ def _local_call_targets(function_node: ast.FunctionDef) -> set[str]:
91
+ """Return the plain-name targets one function directly calls."""
92
+ return {
93
+ each_call.func.id
94
+ for each_call in ast.walk(function_node)
95
+ if isinstance(each_call, ast.Call) and isinstance(each_call.func, ast.Name)
96
+ }
97
+
98
+
99
+ def _unvisited_local_calls(
100
+ function_name: str,
101
+ function_defs_by_name: dict[str, ast.FunctionDef],
102
+ visited: set[str],
103
+ ) -> list[str]:
104
+ """Return *function_name*'s call targets not already visited by the walk."""
105
+ function_node = function_defs_by_name.get(function_name)
106
+ if function_node is None:
107
+ return []
108
+ return [each for each in _local_call_targets(function_node) if each not in visited]
109
+
110
+
111
+ def _reachable_function_names(
112
+ start_name: str, function_defs_by_name: dict[str, ast.FunctionDef]
113
+ ) -> set[str]:
114
+ """Return every function name the call graph reaches, starting from start_name."""
115
+ visited: set[str] = set()
116
+ pending = [start_name]
117
+ while pending:
118
+ each_pending_name = pending.pop()
119
+ if each_pending_name in visited:
120
+ continue
121
+ visited.add(each_pending_name)
122
+ pending.extend(_unvisited_local_calls(each_pending_name, function_defs_by_name, visited))
123
+ return visited
124
+
125
+
126
+ def _referenced_names(function_node: ast.FunctionDef) -> set[str]:
127
+ """Return every loaded identifier one function body references.
128
+
129
+ A callback-style dispatch (``_fragment_or_deferred_check(check_magic_values,
130
+ ...)``) passes the check function by bare name rather than calling it
131
+ directly, so this collects every loaded ``Name``, not only call targets.
132
+ """
133
+ return {
134
+ each_name.id
135
+ for each_name in ast.walk(function_node)
136
+ if isinstance(each_name, ast.Name) and isinstance(each_name.ctx, ast.Load)
64
137
  }
138
+
139
+
140
+ def _reachable_referenced_names(
141
+ start_name: str, function_defs_by_name: dict[str, ast.FunctionDef]
142
+ ) -> set[str]:
143
+ """Return every identifier referenced by a function the call graph reaches."""
144
+ reachable_function_names = _reachable_function_names(start_name, function_defs_by_name)
145
+ referenced_names: set[str] = set()
146
+ for each_function_name in reachable_function_names:
147
+ function_node = function_defs_by_name.get(each_function_name)
148
+ if function_node is not None:
149
+ referenced_names.update(_referenced_names(function_node))
150
+ return referenced_names
151
+
152
+
153
+ def test_every_check_function_is_reachable_from_the_full_gate() -> None:
154
+ module_tree = ast.parse(inspect.getsource(_hook_module))
155
+ function_defs_by_name = _module_level_function_defs(module_tree)
156
+ reachable_names = _reachable_referenced_names(DISPATCH_ENTRY_POINT_NAME, function_defs_by_name)
157
+ all_check_names = set(_all_check_function_names())
158
+ undispatched_check_names = all_check_names - reachable_names
65
159
  unexpected_undispatched = undispatched_check_names - KNOWN_UNDISPATCHED_CHECKS
66
160
  assert unexpected_undispatched == set(), (
67
- f"check_* functions are imported but never called in validate_content: "
68
- f"{sorted(unexpected_undispatched)}. Wire each into validate_content so the "
69
- f"check fires at Write/Edit time, or list it in KNOWN_UNDISPATCHED_CHECKS "
70
- f"with a reason in the test header docstring."
161
+ f"check_* functions are imported but never reachable from "
162
+ f"validate_content_for_full_gate's call graph: {sorted(unexpected_undispatched)}. "
163
+ f"Wire each into validate_content_for_phase or one of its extension-issue "
164
+ f"helpers so the check fires at Write/Edit time, or list it in "
165
+ f"KNOWN_UNDISPATCHED_CHECKS with a reason in the test header docstring."
71
166
  )
72
167
 
73
168
 
@@ -30,7 +30,7 @@ def check_docstring_args_match_signature(content: str, file_path: str) -> list[s
30
30
 
31
31
 
32
32
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
33
- return code_rules_enforcer.validate_content(content, file_path, old_content)
33
+ return code_rules_enforcer.validate_content_for_full_gate(content, file_path, old_content)
34
34
 
35
35
 
36
36
  PRODUCTION_FILE_PATH = "/project/src/services.py"
@@ -39,7 +39,7 @@ def check_docstring_prose_wall_without_illustration(content: str, file_path: str
39
39
 
40
40
 
41
41
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
42
- return code_rules_enforcer.validate_content(content, file_path, old_content)
42
+ return code_rules_enforcer.validate_content_for_full_gate(content, file_path, old_content)
43
43
 
44
44
 
45
45
  PRODUCTION_FILE_PATH = "/project/src/tally.py"
@@ -42,7 +42,7 @@ def check_docstring_runon_sentence(content: str, file_path: str) -> list[str]:
42
42
 
43
43
 
44
44
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
45
- return code_rules_enforcer.validate_content(content, file_path, old_content)
45
+ return code_rules_enforcer.validate_content_for_full_gate(content, file_path, old_content)
46
46
 
47
47
 
48
48
  PRODUCTION_FILE_PATH = "/project/src/run_lifecycle.py"
@@ -32,7 +32,7 @@ def check_documents_unreferenced_parameter(content: str, file_path: str) -> list
32
32
 
33
33
 
34
34
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
35
- return code_rules_enforcer.validate_content(content, file_path, old_content)
35
+ return code_rules_enforcer.validate_content_for_full_gate(content, file_path, old_content)
36
36
 
37
37
 
38
38
  PRODUCTION_FILE_PATH = "/project/src/per_theme_loop.py"
@@ -17,7 +17,7 @@ assert hook_spec.loader is not None
17
17
  hook_module = importlib.util.module_from_spec(hook_spec)
18
18
  hook_spec.loader.exec_module(hook_module)
19
19
  check_fstring_structural_literals = hook_module.check_fstring_structural_literals
20
- validate_content = hook_module.validate_content
20
+ validate_content = hook_module.validate_content_for_full_gate
21
21
 
22
22
  PRODUCTION_FILE_PATH = "packages/claude-dev-env/example.py"
23
23
  TEST_FILE_PATH = "packages/claude-dev-env/test_example.py"
@@ -25,7 +25,7 @@ from code_rules_boolean_mustcheck import ( # noqa: E402
25
25
  check_ignored_must_check_return as _check_ignored_must_check_return,
26
26
  )
27
27
  from code_rules_enforcer import ( # noqa: E402
28
- validate_content as _validate_content,
28
+ validate_content_for_full_gate as _validate_content,
29
29
  )
30
30
 
31
31
  code_rules_enforcer = SimpleNamespace(
@@ -23,7 +23,7 @@ assert hook_spec.loader is not None
23
23
  hook_module = importlib.util.module_from_spec(hook_spec)
24
24
  hook_spec.loader.exec_module(hook_module)
25
25
  check_inline_tuple_string_magic = hook_module.check_inline_tuple_string_magic
26
- validate_content = hook_module.validate_content
26
+ validate_content = hook_module.validate_content_for_full_gate
27
27
 
28
28
  PRODUCTION_FILE_PATH = "packages/app/services/loader.py"
29
29
  TEST_FILE_PATH = "packages/app/services/test_loader.py"
@@ -77,7 +77,7 @@ def test_should_grandfather_unchanged_join_separator_in_wide_edit() -> None:
77
77
  " # keep the join separator the same\n"
78
78
  " return ', '.join(str(each_path) for each_path in all_paths)\n"
79
79
  )
80
- issues = code_rules_enforcer.validate_content(
80
+ issues = code_rules_enforcer.validate_content_for_full_gate(
81
81
  proposed_fragment,
82
82
  PRODUCTION_FILE_PATH,
83
83
  prior_fragment,
@@ -97,7 +97,7 @@ def test_should_block_new_join_separator_in_wide_edit() -> None:
97
97
  "def render(all_paths: list) -> str:\n"
98
98
  " return ', '.join(all_paths)\n"
99
99
  )
100
- issues = code_rules_enforcer.validate_content(
100
+ issues = code_rules_enforcer.validate_content_for_full_gate(
101
101
  proposed_fragment,
102
102
  PRODUCTION_FILE_PATH,
103
103
  prior_fragment,
@@ -1,6 +1,6 @@
1
1
  """Enforcer-dispatch tests for the JS returns-object schema-less branch check.
2
2
 
3
- These drive ``validate_content`` end-to-end on a ``.mjs`` payload, so they prove
3
+ These drive ``validate_content_for_full_gate`` end-to-end on a ``.mjs`` payload, so they prove
4
4
  the check is wired into the JavaScript branch of the enforcer, not just callable
5
5
  in isolation. The drift: a ``function`` whose JSDoc ``@returns {Promise<object>}``
6
6
  promises a structured object while one branch returns the agent helper with an
@@ -62,11 +62,11 @@ def _has_returns_object_finding(all_issues: list[str]) -> bool:
62
62
 
63
63
  def test_enforcer_reports_schema_less_branch_under_returns_object() -> None:
64
64
  drift_source = _mixed_schema_mjs()
65
- all_issues = code_rules_enforcer.validate_content(drift_source, _MJS_PATH, drift_source)
65
+ all_issues = code_rules_enforcer.validate_content_for_full_gate(drift_source, _MJS_PATH, drift_source)
66
66
  assert _has_returns_object_finding(all_issues)
67
67
 
68
68
 
69
69
  def test_enforcer_accepts_every_branch_with_a_schema() -> None:
70
70
  clean_source = _all_schema_mjs()
71
- all_issues = code_rules_enforcer.validate_content(clean_source, _MJS_PATH, clean_source)
71
+ all_issues = code_rules_enforcer.validate_content_for_full_gate(clean_source, _MJS_PATH, clean_source)
72
72
  assert not _has_returns_object_finding(all_issues)
@@ -31,7 +31,7 @@ def check_module_docstring_names_public_checks(content: str, file_path: str) ->
31
31
 
32
32
 
33
33
  def validate_content(content: str, file_path: str, old_content: str) -> list[str]:
34
- return code_rules_enforcer.validate_content(content, file_path, old_content)
34
+ return code_rules_enforcer.validate_content_for_full_gate(content, file_path, old_content)
35
35
 
36
36
 
37
37
  HOOK_INFRASTRUCTURE_PATH = "/home/user/.claude/hooks/blocking/code_rules_test_assertions.py"
@@ -18,7 +18,7 @@ assert _hook_spec.loader is not None
18
18
  _hook_module = importlib.util.module_from_spec(_hook_spec)
19
19
  _hook_spec.loader.exec_module(_hook_module)
20
20
  check_boolean_naming = _hook_module.check_boolean_naming
21
- validate_content = _hook_module.validate_content
21
+ validate_content = _hook_module.validate_content_for_full_gate
22
22
 
23
23
 
24
24
  PRODUCTION_FILE_PATH = "src/app/feature.py"