claude-dev-env 2.28.1 → 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 (157) hide show
  1. package/.agents/agents/AGENTS.md +0 -31
  2. package/.agents/agents/clean-coder.md +1 -1
  3. package/.agents/agents/pr-description-writer.md +21 -6
  4. package/.agents/agents/reference/README.md +6 -0
  5. package/.agents/agents/reference/pr-description-illustrative-voice.md +45 -0
  6. package/.agents/agents/reference/pr-description-verification.md +19 -0
  7. package/.agents/agents/test_agent_frontmatter.py +3 -1
  8. package/.agents/agents/test_pr_description_writer_style.py +47 -0
  9. package/.agents/skills/AGENTS.md +0 -70
  10. package/.agents/skills/_shared/AGENTS.md +0 -44
  11. package/.agents/skills/_shared/advisor/AGENTS.md +0 -8
  12. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -57
  13. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -8
  14. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -34
  15. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -23
  16. package/.agents/skills/autoconverge/AGENTS.md +0 -35
  17. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -15
  18. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -25
  19. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -15
  20. package/.agents/skills/everything-search/AGENTS.md +0 -16
  21. package/.agents/skills/fresh-branch/AGENTS.md +0 -13
  22. package/.agents/skills/grok-spawn/AGENTS.md +0 -27
  23. package/.agents/skills/orchestrator/.claude/CLAUDE.md +1 -0
  24. package/.agents/skills/orchestrator/AGENTS.md +1 -0
  25. package/.agents/skills/orchestrator/SKILL.md +44 -53
  26. package/.agents/skills/orchestrator/reference/.claude/CLAUDE.md +1 -0
  27. package/.agents/skills/orchestrator/reference/AGENTS.md +1 -0
  28. package/.agents/skills/orchestrator/reference/consult-the-orchestrator.md +70 -0
  29. package/.agents/skills/orchestrator/reference/executor-consult-block.md +62 -0
  30. package/.agents/skills/orchestrator/reference/host-detect.md +15 -0
  31. package/.agents/skills/orchestrator/test_orchestrator_skill_contract.py +62 -0
  32. package/.agents/skills/orchestrator-refresh/SKILL.md +16 -34
  33. package/.agents/skills/rebase/AGENTS.md +0 -31
  34. package/.agents/skills/session-log/AGENTS.md +0 -31
  35. package/.agents/skills/session-tidy/AGENTS.md +0 -35
  36. package/.agents/skills/skill-builder/AGENTS.md +0 -48
  37. package/.agents/skills/skill-builder/references/AGENTS.md +0 -24
  38. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -13
  39. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -18
  40. package/.agents/skills/task-build/AGENTS.md +0 -28
  41. package/.agents/skills/update/AGENTS.md +0 -37
  42. package/.agents/skills-archived/AGENTS.md +0 -44
  43. package/.agents/skills-archived/anthropic-plan/AGENTS.md +0 -33
  44. package/.agents/skills-archived/anthropic-plan/scripts/AGENTS.md +0 -10
  45. package/.agents/skills-archived/anthropic-plan/scripts/anthropic_plan_scripts_constants/AGENTS.md +0 -15
  46. package/.agents/skills-archived/anthropic-plan/templates/AGENTS.md +0 -12
  47. package/.agents/skills-archived/anthropic-plan/workflow/AGENTS.md +0 -13
  48. package/.agents/skills-archived/auditing-claude-config/AGENTS.md +0 -20
  49. package/.agents/skills-archived/bugteam/AGENTS.md +0 -29
  50. package/.agents/skills-archived/bugteam/reference/AGENTS.md +0 -19
  51. package/.agents/skills-archived/bugteam/reference/obstacles/AGENTS.md +0 -23
  52. package/.agents/skills-archived/bugteam/scripts/AGENTS.md +0 -29
  53. package/.agents/skills-archived/bugteam/scripts/bugteam_scripts_constants/AGENTS.md +0 -17
  54. package/.agents/skills-archived/codex-review/AGENTS.md +0 -45
  55. package/.agents/skills-archived/codex-review/reference/AGENTS.md +0 -14
  56. package/.agents/skills-archived/codex-review/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
  57. package/.agents/skills-archived/codex-review/test_skill_scaffold.py +13 -3
  58. package/.agents/skills-archived/copilot-review/AGENTS.md +0 -17
  59. package/.agents/skills-archived/pr-converge/AGENTS.md +0 -31
  60. package/.agents/skills-archived/pr-converge/pr_converge_skill_constants/AGENTS.md +0 -25
  61. package/.agents/skills-archived/pr-converge/reference/AGENTS.md +0 -27
  62. package/.agents/skills-archived/pr-converge/reference/obstacles/AGENTS.md +0 -22
  63. package/.agents/skills-archived/pr-converge/scripts/AGENTS.md +0 -45
  64. package/.agents/skills-archived/pr-converge/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
  65. package/.agents/skills-archived/pr-converge/workflows/AGENTS.md +0 -15
  66. package/.agents/skills-archived/recall/AGENTS.md +0 -29
  67. package/.agents/skills-archived/remember/AGENTS.md +0 -30
  68. package/AGENTS.md +0 -112
  69. package/_shared/AGENTS.md +0 -16
  70. package/_shared/advisor/AGENTS.md +0 -21
  71. package/_shared/advisor/advisor-protocol.md +4 -1
  72. package/_shared/advisor/reference/consult-format.md +4 -2
  73. package/_shared/pr-loop/AGENTS.md +0 -27
  74. package/_shared/pr-loop/code-rules-gate.md +1 -1
  75. package/_shared/pr-loop/scripts/AGENTS.md +0 -49
  76. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -2
  77. package/_shared/pr-loop/scripts/code_rules_gate_parts/AGENTS.md +0 -41
  78. package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +8 -8
  79. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +2 -2
  80. package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +1 -1
  81. package/_shared/pr-loop/scripts/codex_review_scripts_constants/AGENTS.md +0 -17
  82. package/_shared/pr-loop/scripts/pr_converge_scripts_constants/AGENTS.md +0 -17
  83. package/_shared/pr-loop/scripts/pr_converge_skill_constants/AGENTS.md +0 -25
  84. package/_shared/pr-loop/scripts/pr_loop_shared_constants/AGENTS.md +0 -25
  85. package/_shared/pr-loop/scripts/tests/AGENTS.md +0 -43
  86. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +15 -15
  87. package/_shared/process-tree/AGENTS.md +0 -40
  88. package/audit-rubrics/AGENTS.md +0 -42
  89. package/audit-rubrics/category_rubrics/AGENTS.md +0 -36
  90. package/audit-rubrics/prompts/AGENTS.md +0 -36
  91. package/bin/AGENTS.md +0 -118
  92. package/bin/install.test.mjs +1 -1
  93. package/commands/AGENTS.md +0 -14
  94. package/docs/AGENTS.md +0 -31
  95. package/docs/CODE_RULES.md +10 -0
  96. package/docs/references/AGENTS.md +0 -16
  97. package/hooks/AGENTS.md +0 -27
  98. package/hooks/advisory/AGENTS.md +0 -15
  99. package/hooks/blocking/AGENTS.md +0 -112
  100. package/hooks/blocking/claude_md_orphan_file_blocker_parts/AGENTS.md +0 -27
  101. package/hooks/blocking/code_rules_enforcer.py +433 -313
  102. package/hooks/blocking/config/AGENTS.md +0 -9
  103. package/hooks/blocking/inventory_intent_records/AGENTS.md +0 -25
  104. package/hooks/blocking/package_inventory_stale_blocker_parts/AGENTS.md +0 -25
  105. package/hooks/blocking/pii_prevention_blocker_parts/AGENTS.md +0 -23
  106. package/hooks/blocking/tdd_enforcer_parts/AGENTS.md +0 -29
  107. package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +1 -1
  108. package/hooks/blocking/test_code_rules_enforcer_class_docstring_methods.py +1 -1
  109. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +117 -22
  110. package/hooks/blocking/test_code_rules_enforcer_docstring_args_signature.py +1 -1
  111. package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +1 -1
  112. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +1 -1
  113. package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +1 -1
  114. package/hooks/blocking/test_code_rules_enforcer_fstring_scan.py +1 -1
  115. package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +1 -1
  116. package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +1 -1
  117. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +2 -2
  118. package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +3 -3
  119. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +1 -1
  120. package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +1 -1
  121. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +117 -38
  122. package/hooks/blocking/test_code_rules_enforcer_output_snapshot.py +121 -0
  123. package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +17 -15
  124. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +4 -2
  125. package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +1 -1
  126. package/hooks/blocking/test_code_rules_enforcer_stage.py +1 -1
  127. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +4 -4
  128. package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +2 -2
  129. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +2 -2
  130. package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +2 -2
  131. package/hooks/blocking/test_code_rules_validation_phase.py +281 -0
  132. package/hooks/blocking/test_fable_spawn_gate.py +1 -1
  133. package/hooks/blocking/test_pre_tool_use_dispatcher.py +26 -11
  134. package/hooks/git-hooks/AGENTS.md +0 -31
  135. package/hooks/git-hooks/git_hooks_constants/AGENTS.md +0 -20
  136. package/hooks/hooks_constants/AGENTS.md +0 -99
  137. package/hooks/hooks_constants/code_rules_enforcer_constants.py +8 -0
  138. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +21 -0
  139. package/hooks/hooks_constants/validation_phase_constants.py +34 -0
  140. package/hooks/lifecycle/AGENTS.md +0 -17
  141. package/hooks/observability/AGENTS.md +0 -19
  142. package/hooks/session/AGENTS.md +0 -37
  143. package/hooks/validation/AGENTS.md +0 -19
  144. package/hooks/validators/AGENTS.md +0 -51
  145. package/hooks/validators/README.md +1 -1
  146. package/hooks/validators/config/hook_timing_harness_constants.py +16 -0
  147. package/hooks/validators/hook_timing_harness.py +287 -0
  148. package/hooks/validators/test_hook_timing_harness.py +251 -0
  149. package/hooks/workflow/AGENTS.md +0 -15
  150. package/output-styles/AGENTS.md +0 -14
  151. package/package.json +1 -1
  152. package/rules/AGENTS.md +0 -55
  153. package/scripts/AGENTS.md +0 -55
  154. package/scripts/dev_env_scripts_constants/AGENTS.md +0 -20
  155. package/scripts/sync_to_cursor/AGENTS.md +0 -22
  156. package/scripts/tests/AGENTS.md +0 -34
  157. package/system-prompts/AGENTS.md +0 -24
@@ -4,7 +4,7 @@
4
4
  This entry module reads a PreToolUse JSON payload on stdin, reconstructs the
5
5
  post-edit content, and runs every applicable check. The individual checks live
6
6
  in focused ``code_rules_<concern>.py`` sibling modules; this module imports the
7
- ones ``validate_content`` calls and orchestrates them.
7
+ ones ``validate_content_for_phase`` calls and orchestrates them.
8
8
 
9
9
  Advisory only (non-blocking):
10
10
  - File line count: stderr warning at 400 lines (soft) and 1000 lines (hard)
@@ -21,7 +21,7 @@ import sys
21
21
  from collections import Counter
22
22
  from collections.abc import Callable
23
23
  from pathlib import Path
24
- from typing import TextIO
24
+ from typing import NamedTuple, TextIO
25
25
 
26
26
  _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
27
27
  _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
@@ -178,9 +178,13 @@ from hooks_constants.code_rules_enforcer_constants import ( # noqa: E402
178
178
  ALL_CODE_EXTENSIONS,
179
179
  ALL_JAVASCRIPT_EXTENSIONS,
180
180
  ALL_PYTHON_EXTENSIONS,
181
+ ALL_VALIDATION_PHASES,
181
182
  DENY_REASON_ISSUE_PREVIEW_COUNT,
183
+ EDIT_LANE_PHASE,
184
+ FULL_GATE_PHASE,
182
185
  PRECHECK_USAGE_EXIT_CODE,
183
186
  PRECHECK_USAGE_MESSAGE,
187
+ UNKNOWN_VALIDATION_PHASE_MESSAGE_TEMPLATE,
184
188
  VIOLATION_SEPARATOR,
185
189
  )
186
190
  from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
@@ -189,6 +193,30 @@ from hooks_constants.setup_project_paths_constants import ( # noqa: E402
189
193
  )
190
194
 
191
195
 
196
+ class _ValidationContext(NamedTuple):
197
+ """Bundle the parameters every extension-issue check function reads."""
198
+
199
+ content: str
200
+ old_content: str
201
+ effective_content: str
202
+ file_path: str
203
+ all_changed_lines: set[int] | None
204
+ defer_scope_to_caller: bool
205
+ sibling_directory: Path | None
206
+ phase: str
207
+
208
+
209
+ def _validated_phase(phase: str) -> str:
210
+ """Return *phase* unchanged, after rejecting a phase outside ALL_VALIDATION_PHASES."""
211
+ if phase not in ALL_VALIDATION_PHASES:
212
+ raise ValueError(
213
+ UNKNOWN_VALIDATION_PHASE_MESSAGE_TEMPLATE.format(
214
+ phase=phase, all_phases=sorted(ALL_VALIDATION_PHASES)
215
+ )
216
+ )
217
+ return phase
218
+
219
+
192
220
  def _codex_patch_issues(each_patch_file: CodexPatchFile) -> list[str]:
193
221
  """Run the existing code-rules verdict over one Codex patch view."""
194
222
  if not each_patch_file.post_content and each_patch_file.operation == "delete":
@@ -199,9 +227,10 @@ def _codex_patch_issues(each_patch_file: CodexPatchFile) -> list[str]:
199
227
  each_patch_file.file_path,
200
228
  each_patch_file.post_content,
201
229
  each_patch_file.prior_content,
230
+ phase=EDIT_LANE_PHASE,
202
231
  )
203
232
  elif _is_validated_target(each_patch_file.file_path):
204
- all_issues = validate_content(
233
+ all_issues = validate_content_for_edit_lane(
205
234
  each_patch_file.post_content,
206
235
  each_patch_file.file_path,
207
236
  each_patch_file.prior_content,
@@ -259,314 +288,396 @@ def _report_codex_patch_payload(
259
288
  )
260
289
 
261
290
 
262
- def validate_content(
263
- content: str,
264
- file_path: str,
265
- old_content: str = "",
266
- full_file_content: str | None = None,
267
- prior_full_file_content: str = "",
268
- defer_scope_to_caller: bool = False,
269
- sibling_directory: Path | None = None,
270
- ) -> list[str]:
271
- """Run all applicable validators on content.
272
-
273
- Args:
274
- content: The new content being written. For Edit, this is the
275
- ``new_string`` fragment; for Write, the entire new file body.
276
- file_path: Path to the file.
277
- old_content: Previous content (old_string for Edit, existing file for Write).
278
- Used to detect comment additions/removals instead of flagging all comments.
279
- full_file_content: For Edit operations, the reconstructed post-edit
280
- content of the entire file (existing file with ``old_string`` replaced
281
- by ``new_string``). Whole-file checks such as the unused-import
282
- scanner use this to evaluate references across the file rather than
283
- just within the inserted fragment.
284
- prior_full_file_content: For Edit operations, the entire file content as
285
- it existed before the edit applied. Whole-file span checks
286
- (function length, test isolation) diff this against
287
- ``full_file_content`` to recover the lines the edit touched, then
288
- block only on violations whose source span intersects those lines —
289
- mirroring the gate's span-intersection scoping. Defaults to the
290
- empty string for Write and for gate invocations, which leaves those
291
- checks scanning the whole file with no diff scoping.
292
- defer_scope_to_caller: The explicit signal that a downstream scoper will
293
- run, used to disambiguate the two callers that supply no changed-line
294
- set. The commit/push gate passes True: it owns
295
- ``split_violations_by_scope`` and classifies blocking vs advisory by
296
- added line, so the function-length, test-isolation, and banned-noun
297
- checks return their violations unscoped for the gate to classify.
298
- PreToolUse new-file or full-file writes leave this False: this
299
- enforcer is terminal, so it marks every violation in scope.
300
- sibling_directory: The absolute directory the cross-file duplicate-body
301
- check scans for sibling modules. The commit/push gate passes the
302
- resolved file's parent so the on-disk sibling scan stays anchored to
303
- the repository regardless of the gate process's working directory.
304
- None (the PreToolUse default) derives the directory from
305
- ``file_path``'s parent, which is already absolute on that path.
306
- """
307
- extension = get_file_extension(file_path)
308
- all_issues = []
309
- effective_content = content if full_file_content is None else full_file_content
310
- all_changed_lines = (
311
- changed_line_numbers(prior_full_file_content, full_file_content)
312
- if full_file_content is not None
313
- else None
291
+ def _python_comment_and_logging_issues(context: _ValidationContext) -> list[str]:
292
+ """Return the comment-diff and logging-hygiene checks for a Python file."""
293
+ content, old_content, file_path = (
294
+ context.content,
295
+ context.old_content,
296
+ context.file_path,
314
297
  )
298
+ all_issues: list[str] = []
299
+ if not is_test_file(file_path):
300
+ all_issues.extend(check_comment_changes(old_content, content, file_path))
301
+ all_issues.extend(check_imports_at_top(content))
302
+ all_issues.extend(check_logging_fstrings(content))
303
+ all_issues.extend(check_logging_printf_tokens(content, file_path))
304
+ all_issues.extend(check_logging_adjacent_string_literals(content, file_path))
305
+ all_issues.extend(check_windows_api_none(content))
306
+ all_issues.extend(check_naive_datetime_construction(content, file_path))
307
+ return all_issues
315
308
 
316
- if extension in ALL_PYTHON_EXTENSIONS:
317
- if not is_test_file(file_path):
318
- all_issues.extend(check_comment_changes(old_content, content, file_path))
319
- all_issues.extend(check_imports_at_top(content))
320
- all_issues.extend(check_logging_fstrings(content))
321
- all_issues.extend(check_logging_printf_tokens(content, file_path))
322
- all_issues.extend(check_logging_adjacent_string_literals(content, file_path))
323
- all_issues.extend(check_windows_api_none(content))
324
- all_issues.extend(check_naive_datetime_construction(content, file_path))
325
- all_issues.extend(
326
- _fragment_or_deferred_check(
327
- check_magic_values,
328
- old_content,
329
- content,
330
- file_path,
331
- defer_scope_to_caller,
332
- )
333
- )
334
- all_issues.extend(
335
- _fragment_or_deferred_check(
336
- check_blast_radius_declared,
337
- old_content,
338
- content,
339
- file_path,
340
- defer_scope_to_caller,
341
- )
342
- )
343
- all_issues.extend(check_fstring_structural_literals(content, file_path))
344
- all_issues.extend(check_constants_outside_config(content, file_path))
345
- all_issues.extend(check_config_duplicate_path_anchor(content, file_path))
346
- all_issues.extend(check_constants_outside_config_advisory(content, file_path))
347
- all_issues.extend(check_file_global_constants_use_count(content, file_path))
348
- all_issues.extend(
349
- check_duplicate_function_body_across_files(
350
- effective_content,
351
- file_path,
352
- all_changed_lines,
353
- defer_scope_to_caller,
354
- sibling_directory,
355
- )
356
- )
357
- all_issues.extend(
358
- check_same_file_inline_duplicate_body(
359
- effective_content,
360
- file_path,
361
- all_changed_lines,
362
- defer_scope_to_caller,
363
- )
364
- )
365
- all_issues.extend(check_type_escape_hatches(effective_content, file_path))
366
- all_issues.extend(
367
- check_banned_identifiers(
368
- effective_content,
369
- file_path,
370
- all_changed_lines,
371
- defer_scope_to_caller,
372
- )
373
- )
374
- all_issues.extend(
375
- check_banned_noun_word_boundary(
376
- effective_content,
377
- file_path,
378
- all_changed_lines,
379
- defer_scope_to_caller,
380
- )
381
- )
382
- all_issues.extend(check_banned_prefixes(effective_content, file_path))
383
- all_issues.extend(check_stub_implementations(effective_content, file_path))
384
- all_issues.extend(check_typed_dict_encode_decode(effective_content, file_path))
385
- all_issues.extend(check_test_branching_in_production(effective_content, file_path))
386
- all_issues.extend(check_dead_test_module_constant(effective_content, file_path))
387
- all_issues.extend(check_unused_test_helper_parameter(effective_content, file_path))
388
- all_issues.extend(check_bare_except(effective_content, file_path))
389
- all_issues.extend(check_thin_wrapper_files(effective_content, file_path))
390
- all_issues.extend(check_zero_payload_function_alias(effective_content, file_path))
391
- all_issues.extend(check_boundary_types(effective_content, file_path))
392
- all_issues.extend(check_docstring_format(effective_content, file_path))
393
- all_issues.extend(check_docstring_args_match_signature(effective_content, file_path))
394
- all_issues.extend(
395
- check_docstring_documents_unreferenced_parameter(effective_content, file_path)
396
- )
397
- all_issues.extend(
398
- check_class_docstring_names_public_methods(effective_content, file_path)
399
- )
400
- all_issues.extend(
401
- check_docstring_runon_sentence(
402
- effective_content,
403
- file_path,
404
- all_changed_lines,
405
- defer_scope_to_caller,
406
- )
407
- )
408
- all_issues.extend(
409
- check_docstring_prose_wall_without_illustration(
410
- effective_content,
411
- file_path,
412
- all_changed_lines,
413
- defer_scope_to_caller,
414
- )
415
- )
416
- all_issues.extend(
417
- check_module_docstring_names_public_checks(effective_content, file_path)
418
- )
419
- all_issues.extend(
420
- check_module_docstring_scope_omits_data_schema_constants(
421
- effective_content, file_path
422
- )
423
- )
424
- all_issues.extend(
425
- check_docstring_names_undefined_constant(effective_content, file_path)
426
- )
427
- all_issues.extend(
428
- check_boolean_naming(
429
- effective_content,
430
- file_path,
431
- all_changed_lines,
432
- defer_scope_to_caller,
433
- )
434
- )
435
- all_issues.extend(
436
- check_ignored_must_check_return(
437
- effective_content,
438
- file_path,
439
- all_changed_lines,
440
- defer_scope_to_caller,
441
- )
442
- )
443
- all_issues.extend(check_skip_decorators_in_tests(content, file_path))
444
- all_issues.extend(
445
- check_tests_use_isolated_filesystem_paths(
446
- effective_content,
447
- file_path,
448
- all_changed_lines,
449
- defer_scope_to_caller,
450
- )
451
- )
452
- all_issues.extend(check_existence_check_tests(content, file_path))
453
- all_issues.extend(check_constant_equality_tests(content, file_path))
454
- all_issues.extend(check_vacuous_cleanup_assertion_tests(content, file_path))
455
- all_issues.extend(check_stale_test_name_target(content, file_path))
456
- check_flag_gated_scenario_test_naming(content, file_path)
457
- all_issues.extend(check_unused_optional_parameters(content, file_path))
458
- all_issues.extend(check_collection_prefix(content, file_path))
459
- all_issues.extend(check_stuttering_collection_prefix(content, file_path))
460
- all_issues.extend(check_hardcoded_user_paths(content, file_path))
461
- all_issues.extend(check_sys_path_insert_deduplication_guard(content, file_path))
462
- all_issues.extend(check_library_print(content, file_path))
463
- all_issues.extend(check_parameter_annotations(content, file_path))
464
- all_issues.extend(check_known_pytest_fixture_annotations(content, file_path))
465
- all_issues.extend(
466
- check_unused_known_pytest_fixture_parameters(content, file_path)
467
- )
468
- all_issues.extend(
469
- check_return_annotations(
470
- effective_content,
471
- file_path,
472
- all_changed_lines,
473
- defer_scope_to_caller,
474
- )
475
- )
476
- all_issues.extend(
477
- check_function_length(
478
- effective_content,
479
- file_path,
480
- all_changed_lines,
481
- defer_scope_to_caller,
482
- )
309
+
310
+ def _python_magic_value_and_constant_issues(context: _ValidationContext) -> list[str]:
311
+ """Return the magic-value and constants-location checks for a Python file."""
312
+ old_content, content, file_path = (
313
+ context.old_content,
314
+ context.content,
315
+ context.file_path,
316
+ )
317
+ defer = context.defer_scope_to_caller
318
+ all_issues = _fragment_or_deferred_check(
319
+ check_magic_values, old_content, content, file_path, defer
320
+ )
321
+ all_issues.extend(
322
+ _fragment_or_deferred_check(
323
+ check_blast_radius_declared, old_content, content, file_path, defer
483
324
  )
484
- all_issues.extend(
485
- check_public_function_missing_paired_test(
486
- effective_content,
487
- file_path,
488
- all_changed_lines,
489
- defer_scope_to_caller,
490
- )
325
+ )
326
+ all_issues.extend(check_fstring_structural_literals(content, file_path))
327
+ all_issues.extend(check_constants_outside_config(content, file_path))
328
+ all_issues.extend(check_constants_outside_config_advisory(content, file_path))
329
+ all_issues.extend(check_file_global_constants_use_count(content, file_path))
330
+ return all_issues
331
+
332
+
333
+ def _python_duplicate_body_and_banned_issues(context: _ValidationContext) -> list[str]:
334
+ """Return the same-file duplicate-body and banned-name checks for Python."""
335
+ effective_content, file_path = context.effective_content, context.file_path
336
+ changed, defer = context.all_changed_lines, context.defer_scope_to_caller
337
+ all_issues = check_same_file_inline_duplicate_body(
338
+ effective_content, file_path, changed, defer
339
+ )
340
+ all_issues.extend(check_type_escape_hatches(effective_content, file_path))
341
+ all_issues.extend(
342
+ check_banned_identifiers(effective_content, file_path, changed, defer)
343
+ )
344
+ all_issues.extend(
345
+ check_banned_noun_word_boundary(effective_content, file_path, changed, defer)
346
+ )
347
+ all_issues.extend(check_banned_prefixes(effective_content, file_path))
348
+ return all_issues
349
+
350
+
351
+ def _python_structure_and_stub_issues(context: _ValidationContext) -> list[str]:
352
+ """Return the stub, wrapper, and structural checks for a Python file."""
353
+ effective_content, file_path = context.effective_content, context.file_path
354
+ all_issues = check_stub_implementations(effective_content, file_path)
355
+ all_issues.extend(check_typed_dict_encode_decode(effective_content, file_path))
356
+ all_issues.extend(check_test_branching_in_production(effective_content, file_path))
357
+ all_issues.extend(check_dead_test_module_constant(effective_content, file_path))
358
+ all_issues.extend(check_unused_test_helper_parameter(effective_content, file_path))
359
+ all_issues.extend(check_bare_except(effective_content, file_path))
360
+ all_issues.extend(check_thin_wrapper_files(effective_content, file_path))
361
+ all_issues.extend(check_zero_payload_function_alias(effective_content, file_path))
362
+ all_issues.extend(check_boundary_types(effective_content, file_path))
363
+ return all_issues
364
+
365
+
366
+ def _python_docstring_format_issues(context: _ValidationContext) -> list[str]:
367
+ """Return the docstring-format-vs-signature checks for a Python file."""
368
+ effective_content, file_path = context.effective_content, context.file_path
369
+ all_issues = check_docstring_format(effective_content, file_path)
370
+ all_issues.extend(
371
+ check_docstring_args_match_signature(effective_content, file_path)
372
+ )
373
+ all_issues.extend(
374
+ check_docstring_documents_unreferenced_parameter(effective_content, file_path)
375
+ )
376
+ all_issues.extend(
377
+ check_class_docstring_names_public_methods(effective_content, file_path)
378
+ )
379
+ return all_issues
380
+
381
+
382
+ def _python_docstring_narrative_issues(context: _ValidationContext) -> list[str]:
383
+ """Return the docstring narrative-prose checks for a Python file."""
384
+ effective_content, file_path = context.effective_content, context.file_path
385
+ changed, defer = context.all_changed_lines, context.defer_scope_to_caller
386
+ all_issues = check_docstring_runon_sentence(
387
+ effective_content, file_path, changed, defer
388
+ )
389
+ all_issues.extend(
390
+ check_docstring_prose_wall_without_illustration(
391
+ effective_content, file_path, changed, defer
491
392
  )
492
- all_issues.extend(
493
- check_test_file_omits_module_public_function(
494
- effective_content,
495
- file_path,
496
- )
393
+ )
394
+ all_issues.extend(
395
+ check_module_docstring_names_public_checks(effective_content, file_path)
396
+ )
397
+ all_issues.extend(
398
+ check_module_docstring_scope_omits_data_schema_constants(
399
+ effective_content, file_path
497
400
  )
498
- all_issues.extend(check_loop_variable_naming(content, file_path))
499
- all_issues.extend(check_referenced_underscore_loop_variable(content, file_path))
500
- all_issues.extend(check_polarity_name_contradiction(content, file_path))
501
- all_issues.extend(check_inline_literal_collections(content, file_path))
502
- all_issues.extend(check_inline_tuple_string_magic(content, file_path))
503
- all_issues.extend(
504
- _fragment_or_deferred_check(
505
- check_join_separator_string_magic,
506
- old_content,
507
- content,
508
- file_path,
509
- defer_scope_to_caller,
510
- )
401
+ )
402
+ all_issues.extend(
403
+ check_docstring_names_undefined_constant(effective_content, file_path)
404
+ )
405
+ return all_issues
406
+
407
+
408
+ def _python_boolean_and_test_assertion_issues(context: _ValidationContext) -> list[str]:
409
+ """Return the boolean-naming and test-assertion-quality checks for Python."""
410
+ content, effective_content, file_path = (
411
+ context.content,
412
+ context.effective_content,
413
+ context.file_path,
414
+ )
415
+ changed, defer = context.all_changed_lines, context.defer_scope_to_caller
416
+ all_issues = check_boolean_naming(effective_content, file_path, changed, defer)
417
+ all_issues.extend(
418
+ check_ignored_must_check_return(effective_content, file_path, changed, defer)
419
+ )
420
+ all_issues.extend(check_skip_decorators_in_tests(content, file_path))
421
+ all_issues.extend(
422
+ check_tests_use_isolated_filesystem_paths(
423
+ effective_content, file_path, changed, defer
511
424
  )
512
- all_issues.extend(
513
- _fragment_or_deferred_check(
514
- check_string_literal_magic,
515
- old_content,
516
- content,
517
- file_path,
518
- defer_scope_to_caller,
519
- )
425
+ )
426
+ all_issues.extend(check_existence_check_tests(content, file_path))
427
+ all_issues.extend(check_constant_equality_tests(content, file_path))
428
+ all_issues.extend(check_vacuous_cleanup_assertion_tests(content, file_path))
429
+ all_issues.extend(check_stale_test_name_target(content, file_path))
430
+ check_flag_gated_scenario_test_naming(content, file_path)
431
+ return all_issues
432
+
433
+
434
+ def _python_naming_and_annotation_issues(context: _ValidationContext) -> list[str]:
435
+ """Return the naming-convention and annotation checks for a Python file."""
436
+ content, file_path = context.content, context.file_path
437
+ all_issues = check_unused_optional_parameters(content, file_path)
438
+ all_issues.extend(check_collection_prefix(content, file_path))
439
+ all_issues.extend(check_stuttering_collection_prefix(content, file_path))
440
+ all_issues.extend(check_hardcoded_user_paths(content, file_path))
441
+ all_issues.extend(check_sys_path_insert_deduplication_guard(content, file_path))
442
+ all_issues.extend(check_library_print(content, file_path))
443
+ all_issues.extend(check_parameter_annotations(content, file_path))
444
+ all_issues.extend(check_known_pytest_fixture_annotations(content, file_path))
445
+ all_issues.extend(check_unused_known_pytest_fixture_parameters(content, file_path))
446
+ all_issues.extend(
447
+ check_return_annotations(
448
+ context.effective_content,
449
+ file_path,
450
+ context.all_changed_lines,
451
+ context.defer_scope_to_caller,
520
452
  )
521
- all_issues.extend(check_whitespace_indentation_magic(content, file_path))
522
- all_issues.extend(check_orphan_css_classes(effective_content, file_path))
523
- check_duplicated_format_patterns(content, file_path)
524
- advise_cross_skill_duplicate_helper(effective_content, file_path)
453
+ )
454
+ return all_issues
525
455
 
526
- elif extension in ALL_JAVASCRIPT_EXTENSIONS:
527
- if not is_test_file(file_path):
528
- all_issues.extend(check_comment_changes(old_content, content, file_path))
529
- all_issues.extend(check_e2e_test_naming(content, file_path))
530
- all_issues.extend(
531
- check_js_boolean_naming(
532
- effective_content,
533
- file_path,
534
- all_changed_lines,
535
- defer_scope_to_caller,
536
- )
456
+
457
+ def _python_function_length_and_naming_issues(context: _ValidationContext) -> list[str]:
458
+ """Return the function-length and loop/name-contradiction checks for Python."""
459
+ content, file_path = context.content, context.file_path
460
+ all_issues = check_function_length(
461
+ context.effective_content,
462
+ file_path,
463
+ context.all_changed_lines,
464
+ context.defer_scope_to_caller,
465
+ )
466
+ all_issues.extend(check_loop_variable_naming(content, file_path))
467
+ all_issues.extend(check_referenced_underscore_loop_variable(content, file_path))
468
+ all_issues.extend(check_polarity_name_contradiction(content, file_path))
469
+ return all_issues
470
+
471
+
472
+ def _python_string_magic_issues(context: _ValidationContext) -> list[str]:
473
+ """Return the inline-literal and string-magic checks for a Python file."""
474
+ content, old_content, file_path = (
475
+ context.content,
476
+ context.old_content,
477
+ context.file_path,
478
+ )
479
+ defer = context.defer_scope_to_caller
480
+ all_issues = [*check_inline_literal_collections(content, file_path)]
481
+ all_issues.extend(check_inline_tuple_string_magic(content, file_path))
482
+ all_issues.extend(
483
+ _fragment_or_deferred_check(
484
+ check_join_separator_string_magic, old_content, content, file_path, defer
537
485
  )
538
- all_issues.extend(
539
- check_js_banned_identifiers(
540
- effective_content,
541
- file_path,
542
- all_changed_lines,
543
- defer_scope_to_caller,
544
- )
486
+ )
487
+ all_issues.extend(
488
+ _fragment_or_deferred_check(
489
+ check_string_literal_magic, old_content, content, file_path, defer
545
490
  )
546
- all_issues.extend(
547
- check_js_resume_task_enumeration_coverage(content, file_path)
491
+ )
492
+ all_issues.extend(check_whitespace_indentation_magic(content, file_path))
493
+ check_duplicated_format_patterns(content, file_path)
494
+ return all_issues
495
+
496
+
497
+ def _python_full_gate_only_issues(context: _ValidationContext) -> list[str]:
498
+ """Return the six cross-file checks that read sibling modules, full-gate only."""
499
+ if context.phase != FULL_GATE_PHASE:
500
+ return []
501
+ content, effective_content, file_path = (
502
+ context.content,
503
+ context.effective_content,
504
+ context.file_path,
505
+ )
506
+ changed, defer = context.all_changed_lines, context.defer_scope_to_caller
507
+ all_issues = check_config_duplicate_path_anchor(content, file_path)
508
+ all_issues.extend(
509
+ check_duplicate_function_body_across_files(
510
+ effective_content, file_path, changed, defer, context.sibling_directory
548
511
  )
549
- all_issues.extend(
550
- check_js_returns_object_schemaless_branch(content, file_path)
512
+ )
513
+ all_issues.extend(
514
+ check_public_function_missing_paired_test(
515
+ effective_content, file_path, changed, defer
551
516
  )
552
- all_issues.extend(
553
- check_js_sibling_return_object_key_drift(content, file_path)
517
+ )
518
+ all_issues.extend(
519
+ check_test_file_omits_module_public_function(effective_content, file_path)
520
+ )
521
+ all_issues.extend(check_orphan_css_classes(effective_content, file_path))
522
+ advise_cross_skill_duplicate_helper(effective_content, file_path)
523
+ return all_issues
524
+
525
+
526
+ def _python_extension_issues(context: _ValidationContext) -> list[str]:
527
+ """Return every issue a Python-extension target can raise."""
528
+ all_issues = _python_comment_and_logging_issues(context)
529
+ all_issues.extend(_python_magic_value_and_constant_issues(context))
530
+ all_issues.extend(_python_duplicate_body_and_banned_issues(context))
531
+ all_issues.extend(_python_structure_and_stub_issues(context))
532
+ all_issues.extend(_python_docstring_format_issues(context))
533
+ all_issues.extend(_python_docstring_narrative_issues(context))
534
+ all_issues.extend(_python_boolean_and_test_assertion_issues(context))
535
+ all_issues.extend(_python_naming_and_annotation_issues(context))
536
+ all_issues.extend(_python_function_length_and_naming_issues(context))
537
+ all_issues.extend(_python_string_magic_issues(context))
538
+ all_issues.extend(_python_full_gate_only_issues(context))
539
+ return all_issues
540
+
541
+
542
+ def _javascript_comment_and_naming_issues(context: _ValidationContext) -> list[str]:
543
+ """Return the comment-diff, e2e-naming, and boolean-naming checks for JavaScript."""
544
+ content, old_content, file_path = (
545
+ context.content,
546
+ context.old_content,
547
+ context.file_path,
548
+ )
549
+ all_issues: list[str] = []
550
+ if not is_test_file(file_path):
551
+ all_issues.extend(check_comment_changes(old_content, content, file_path))
552
+ all_issues.extend(check_e2e_test_naming(content, file_path))
553
+ all_issues.extend(
554
+ check_js_boolean_naming(
555
+ context.effective_content,
556
+ file_path,
557
+ context.all_changed_lines,
558
+ context.defer_scope_to_caller,
554
559
  )
555
- all_issues.extend(
556
- check_js_bare_flag_return_directive(
557
- effective_content,
558
- file_path,
559
- all_changed_lines,
560
- defer_scope_to_caller,
561
- )
560
+ )
561
+ return all_issues
562
+
563
+
564
+ def _javascript_structure_issues(context: _ValidationContext) -> list[str]:
565
+ """Return the banned-identifier and object-shape checks for JavaScript."""
566
+ content, effective_content, file_path = (
567
+ context.content,
568
+ context.effective_content,
569
+ context.file_path,
570
+ )
571
+ changed, defer = context.all_changed_lines, context.defer_scope_to_caller
572
+ all_issues = check_js_banned_identifiers(
573
+ effective_content, file_path, changed, defer
574
+ )
575
+ all_issues.extend(check_js_resume_task_enumeration_coverage(content, file_path))
576
+ all_issues.extend(check_js_returns_object_schemaless_branch(content, file_path))
577
+ all_issues.extend(check_js_sibling_return_object_key_drift(content, file_path))
578
+ all_issues.extend(
579
+ check_js_bare_flag_return_directive(
580
+ effective_content, file_path, changed, defer
562
581
  )
582
+ )
583
+ return all_issues
584
+
585
+
586
+ def _javascript_extension_issues(context: _ValidationContext) -> list[str]:
587
+ """Return every issue a JavaScript-extension target can raise."""
588
+ all_issues = _javascript_comment_and_naming_issues(context)
589
+ all_issues.extend(_javascript_structure_issues(context))
590
+ return all_issues
591
+
592
+
593
+ def _effective_content_and_changed_lines(
594
+ content: str, full_file_content: str | None, prior_full_file_content: str
595
+ ) -> tuple[str, set[int] | None]:
596
+ """Return the effective content and changed-line set an Edit reconstructs."""
597
+ effective_content = content if full_file_content is None else full_file_content
598
+ all_changed_lines = (
599
+ changed_line_numbers(prior_full_file_content, full_file_content)
600
+ if full_file_content is not None
601
+ else None
602
+ )
603
+ return effective_content, all_changed_lines
563
604
 
605
+
606
+ def validate_content_for_phase(
607
+ content: str,
608
+ file_path: str,
609
+ old_content: str = "",
610
+ full_file_content: str | None = None,
611
+ prior_full_file_content: str = "",
612
+ defer_scope_to_caller: bool = False,
613
+ sibling_directory: Path | None = None,
614
+ *,
615
+ phase: str,
616
+ ) -> list[str]:
617
+ """Run all applicable validators on content for one named validation phase."""
618
+ validated_phase = _validated_phase(phase)
619
+ extension = get_file_extension(file_path)
620
+ effective_content, all_changed_lines = _effective_content_and_changed_lines(
621
+ content, full_file_content, prior_full_file_content
622
+ )
623
+ context = _ValidationContext(
624
+ content, old_content, effective_content, file_path, all_changed_lines,
625
+ defer_scope_to_caller, sibling_directory, validated_phase,
626
+ )
627
+ all_issues: list[str] = []
628
+ if extension in ALL_PYTHON_EXTENSIONS:
629
+ all_issues = _python_extension_issues(context)
630
+ elif extension in ALL_JAVASCRIPT_EXTENSIONS:
631
+ all_issues = _javascript_extension_issues(context)
564
632
  if extension in ALL_CODE_EXTENSIONS:
565
633
  advise_file_line_count(content, file_path)
566
-
567
634
  return all_issues
568
635
 
569
636
 
637
+ def validate_content_for_edit_lane(
638
+ content: str,
639
+ file_path: str,
640
+ old_content: str = "",
641
+ full_file_content: str | None = None,
642
+ prior_full_file_content: str = "",
643
+ defer_scope_to_caller: bool = False,
644
+ sibling_directory: Path | None = None,
645
+ ) -> list[str]:
646
+ """Run the edit-lane phase, skipping the six checks that read sibling files."""
647
+ return validate_content_for_phase(
648
+ content,
649
+ file_path,
650
+ old_content,
651
+ full_file_content,
652
+ prior_full_file_content,
653
+ defer_scope_to_caller,
654
+ sibling_directory,
655
+ phase=EDIT_LANE_PHASE,
656
+ )
657
+
658
+
659
+ def validate_content_for_full_gate(
660
+ content: str,
661
+ file_path: str,
662
+ old_content: str = "",
663
+ full_file_content: str | None = None,
664
+ prior_full_file_content: str = "",
665
+ defer_scope_to_caller: bool = False,
666
+ sibling_directory: Path | None = None,
667
+ ) -> list[str]:
668
+ """Run the full-gate phase, including the six checks that read sibling files."""
669
+ return validate_content_for_phase(
670
+ content,
671
+ file_path,
672
+ old_content,
673
+ full_file_content,
674
+ prior_full_file_content,
675
+ defer_scope_to_caller,
676
+ sibling_directory,
677
+ phase=FULL_GATE_PHASE,
678
+ )
679
+
680
+
570
681
  def _read_existing_file_content(file_path: str) -> str | None:
571
682
  """Return the on-disk content of *file_path*, or None when it cannot be read."""
572
683
  try:
@@ -660,17 +771,20 @@ def _hook_infrastructure_blocking_issues(
660
771
  file_path: str,
661
772
  full_file_content: str | None = None,
662
773
  prior_full_file_content: str = "",
774
+ *,
775
+ phase: str,
663
776
  ) -> list[str]:
664
- """Run the checks that still guard a hook Python target.
777
+ """Run the checks that still guard a hook Python target for one phase.
665
778
 
666
779
  The whole code-rules verdict stays off hook-infrastructure files, so this
667
- runs the checks that must still guard them: the cross-file duplicate-body
668
- check and the same-file inline-duplicate-body check, each span-scoped to the
669
- lines an edit touched exactly as ``validate_content`` scopes it for production
670
- code; the zero-payload alias check, whose docstring names hook modules as
671
- its motivating case, run over the whole post-edit file; and the
672
- unanchored command-dispatch check, which guards a ``hooks/blocking``
673
- command classifier against matching a multi-word command as a substring.
780
+ runs the checks that must still guard them in every phase: the same-file
781
+ inline-duplicate-body check and the unanchored command-dispatch check,
782
+ each span-scoped to the lines an edit touched exactly as
783
+ ``validate_content_for_phase`` scopes it for production code; and the
784
+ zero-payload alias check, whose docstring names hook modules as its
785
+ motivating case, run over the whole post-edit file. The cross-file
786
+ duplicate-body check joins that roster only when ``phase`` is
787
+ ``FULL_GATE_PHASE``.
674
788
 
675
789
  Args:
676
790
  content: The fragment or whole-file body under validation.
@@ -679,10 +793,11 @@ def _hook_infrastructure_blocking_issues(
679
793
  None for a whole-file Write.
680
794
  prior_full_file_content: The file content before the edit applied, used to
681
795
  recover the changed lines on an Edit.
796
+ phase: ``EDIT_LANE_PHASE`` or ``FULL_GATE_PHASE``; selects whether the
797
+ cross-file duplicate-body check runs.
682
798
 
683
799
  Returns:
684
- The in-scope duplicate-body violations and the zero-payload alias
685
- violations for the target.
800
+ The in-scope violations for the target at the given phase.
686
801
  """
687
802
  effective_content = content if full_file_content is None else full_file_content
688
803
  all_changed_lines = (
@@ -690,11 +805,15 @@ def _hook_infrastructure_blocking_issues(
690
805
  if full_file_content is not None
691
806
  else None
692
807
  )
693
- all_issues = check_duplicate_function_body_across_files(
694
- effective_content,
695
- file_path,
696
- all_changed_lines,
697
- )
808
+ all_issues: list[str] = []
809
+ if phase == FULL_GATE_PHASE:
810
+ all_issues.extend(
811
+ check_duplicate_function_body_across_files(
812
+ effective_content,
813
+ file_path,
814
+ all_changed_lines,
815
+ )
816
+ )
698
817
  all_issues.extend(
699
818
  check_same_file_inline_duplicate_body(
700
819
  effective_content,
@@ -885,7 +1004,7 @@ def _forecast_full_file_violations(
885
1004
  Returns:
886
1005
  The full-file violations not already in ``all_blocking_issues``.
887
1006
  """
888
- all_full_file_issues = validate_content(
1007
+ all_full_file_issues = validate_content_for_edit_lane(
889
1008
  full_file_content_after_edit, file_path, prior_full_file_content
890
1009
  )
891
1010
  return _issues_absent_from_prior_bodies(all_full_file_issues, all_blocking_issues)
@@ -939,10 +1058,10 @@ def _run_precheck(
939
1058
  candidate_content = candidate_content.lstrip(UTF8_BYTE_ORDER_MARK)
940
1059
  if runs_full_verdict:
941
1060
  old_content = _read_existing_file_content(target_path) or ""
942
- all_issues = validate_content(candidate_content, target_path, old_content)
1061
+ all_issues = validate_content_for_full_gate(candidate_content, target_path, old_content)
943
1062
  else:
944
1063
  all_issues = _hook_infrastructure_blocking_issues(
945
- candidate_content, target_path
1064
+ candidate_content, target_path, phase=FULL_GATE_PHASE
946
1065
  )
947
1066
  for each_issue in all_issues:
948
1067
  violation_stream.write(f"{each_issue}\n")
@@ -1140,7 +1259,7 @@ def _report_blocking_violations(
1140
1259
  prior_full_file_content: The on-disk content before the edit.
1141
1260
  deny_stream: The stream the JSON deny payload is written to.
1142
1261
  """
1143
- all_blocking_issues = validate_content(
1262
+ all_blocking_issues = validate_content_for_edit_lane(
1144
1263
  content,
1145
1264
  file_path,
1146
1265
  old_content,
@@ -1170,9 +1289,9 @@ def _report_hook_blocking_issues(
1170
1289
  ) -> None:
1171
1290
  """Write a deny payload when a hook target trips a check that still guards it.
1172
1291
 
1173
- The full code-rules verdict stays off hook-infrastructure files; this runs the
1174
- two checks that must still guard them — the cross-file duplicate-body check and
1175
- the zero-payload alias check — and emits the deny payload when either fires.
1292
+ The full code-rules verdict stays off hook-infrastructure files; this runs
1293
+ the edit-lane hook-infrastructure checks and emits the deny payload when
1294
+ one fires.
1176
1295
 
1177
1296
  Args:
1178
1297
  content: The fragment or whole-file body under validation.
@@ -1187,6 +1306,7 @@ def _report_hook_blocking_issues(
1187
1306
  file_path,
1188
1307
  full_file_content_after_edit,
1189
1308
  prior_full_file_content,
1309
+ phase=EDIT_LANE_PHASE,
1190
1310
  )
1191
1311
  if not all_blocking_issues:
1192
1312
  return