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.
- package/.agents/agents/pr-description-writer.md +21 -6
- package/.agents/agents/reference/README.md +6 -0
- package/.agents/agents/reference/pr-description-illustrative-voice.md +45 -0
- package/.agents/agents/reference/pr-description-verification.md +19 -0
- package/.agents/agents/test_pr_description_writer_style.py +47 -0
- package/.agents/skills/e-simplify/SKILL.md +20 -8
- package/_shared/pr-loop/code-rules-gate.md +1 -1
- package/_shared/pr-loop/scripts/code_rules_gate.py +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +8 -8
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +1 -1
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +15 -15
- package/docs/CODE_RULES.md +10 -0
- package/hooks/blocking/code_rules_enforcer.py +435 -318
- package/hooks/blocking/shell_substitution_blocker.py +106 -30
- package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +14 -0
- package/hooks/blocking/test_code_rules_enforcer_banned_noun_word.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_class_docstring_methods.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +117 -22
- package/hooks/blocking/test_code_rules_enforcer_docstring_args_signature.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_docstring_prose_wall_illustration.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_docstring_unreferenced_param.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_fstring_scan.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_ignored_must_check_return.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_inline_tuple_string_magic.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_js_returns_object.py +3 -3
- package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_naming_pattern.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +117 -38
- package/hooks/blocking/test_code_rules_enforcer_output_snapshot.py +121 -0
- package/hooks/blocking/test_code_rules_enforcer_precheck_forecast.py +17 -15
- package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +4 -2
- package/hooks/blocking/test_code_rules_enforcer_split_entry_2.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_stage.py +1 -1
- package/hooks/blocking/test_code_rules_enforcer_string_magic.py +4 -4
- package/hooks/blocking/test_code_rules_enforcer_thin_wrapper_files.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias.py +2 -2
- package/hooks/blocking/test_code_rules_enforcer_zero_payload_alias_hook_routing.py +2 -2
- package/hooks/blocking/test_code_rules_validation_phase.py +332 -0
- package/hooks/blocking/test_fable_spawn_gate.py +1 -1
- package/hooks/blocking/test_pre_tool_use_dispatcher.py +26 -11
- package/hooks/blocking/test_shell_substitution_blocker.py +69 -0
- package/hooks/hooks.json +1 -1
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +7 -0
- package/hooks/hooks_constants/shell_substitution_blocker_constants.py +4 -1
- package/hooks/hooks_constants/task_tool_prompt_constants.py +13 -0
- package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +12 -0
- package/hooks/hooks_constants/validation_phase_constants.py +34 -0
- package/hooks/session/task_tool_prompt.py +37 -0
- package/hooks/session/test_task_tool_prompt.py +65 -0
- package/hooks/validators/README.md +1 -1
- package/hooks/validators/config/hook_timing_harness_constants.py +16 -0
- package/hooks/validators/hook_timing_harness.py +287 -0
- package/hooks/validators/test_hook_timing_harness.py +251 -0
- package/package.json +1 -1
- package/hooks/hooks_constants/task_list_loop_starter_constants.py +0 -19
- package/hooks/session/task_list_loop_starter.py +0 -45
- package/hooks/session/test_task_list_loop_starter.py +0 -63
|
@@ -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 ``
|
|
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 =
|
|
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
|
|
263
|
-
|
|
264
|
-
file_path
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
522
|
-
|
|
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
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
547
|
-
|
|
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
|
-
|
|
550
|
-
|
|
512
|
+
)
|
|
513
|
+
all_issues.extend(
|
|
514
|
+
check_public_function_missing_paired_test(
|
|
515
|
+
effective_content, file_path, changed, defer
|
|
551
516
|
)
|
|
552
|
-
|
|
553
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
|
563
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
|
|
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
|
|
668
|
-
check and the
|
|
669
|
-
lines an edit touched exactly as
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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,22 +793,24 @@ 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
|
|
685
|
-
violations for the target.
|
|
800
|
+
The in-scope violations for the target at the given phase.
|
|
686
801
|
"""
|
|
687
|
-
effective_content =
|
|
688
|
-
|
|
689
|
-
changed_line_numbers(prior_full_file_content, full_file_content)
|
|
690
|
-
if full_file_content is not None
|
|
691
|
-
else None
|
|
692
|
-
)
|
|
693
|
-
all_issues = check_duplicate_function_body_across_files(
|
|
694
|
-
effective_content,
|
|
695
|
-
file_path,
|
|
696
|
-
all_changed_lines,
|
|
802
|
+
effective_content, all_changed_lines = _effective_content_and_changed_lines(
|
|
803
|
+
content, full_file_content, prior_full_file_content
|
|
697
804
|
)
|
|
805
|
+
all_issues: list[str] = []
|
|
806
|
+
if phase == FULL_GATE_PHASE:
|
|
807
|
+
all_issues.extend(
|
|
808
|
+
check_duplicate_function_body_across_files(
|
|
809
|
+
effective_content,
|
|
810
|
+
file_path,
|
|
811
|
+
all_changed_lines,
|
|
812
|
+
)
|
|
813
|
+
)
|
|
698
814
|
all_issues.extend(
|
|
699
815
|
check_same_file_inline_duplicate_body(
|
|
700
816
|
effective_content,
|
|
@@ -885,7 +1001,7 @@ def _forecast_full_file_violations(
|
|
|
885
1001
|
Returns:
|
|
886
1002
|
The full-file violations not already in ``all_blocking_issues``.
|
|
887
1003
|
"""
|
|
888
|
-
all_full_file_issues =
|
|
1004
|
+
all_full_file_issues = validate_content_for_edit_lane(
|
|
889
1005
|
full_file_content_after_edit, file_path, prior_full_file_content
|
|
890
1006
|
)
|
|
891
1007
|
return _issues_absent_from_prior_bodies(all_full_file_issues, all_blocking_issues)
|
|
@@ -939,10 +1055,10 @@ def _run_precheck(
|
|
|
939
1055
|
candidate_content = candidate_content.lstrip(UTF8_BYTE_ORDER_MARK)
|
|
940
1056
|
if runs_full_verdict:
|
|
941
1057
|
old_content = _read_existing_file_content(target_path) or ""
|
|
942
|
-
all_issues =
|
|
1058
|
+
all_issues = validate_content_for_full_gate(candidate_content, target_path, old_content)
|
|
943
1059
|
else:
|
|
944
1060
|
all_issues = _hook_infrastructure_blocking_issues(
|
|
945
|
-
candidate_content, target_path
|
|
1061
|
+
candidate_content, target_path, phase=FULL_GATE_PHASE
|
|
946
1062
|
)
|
|
947
1063
|
for each_issue in all_issues:
|
|
948
1064
|
violation_stream.write(f"{each_issue}\n")
|
|
@@ -1140,7 +1256,7 @@ def _report_blocking_violations(
|
|
|
1140
1256
|
prior_full_file_content: The on-disk content before the edit.
|
|
1141
1257
|
deny_stream: The stream the JSON deny payload is written to.
|
|
1142
1258
|
"""
|
|
1143
|
-
all_blocking_issues =
|
|
1259
|
+
all_blocking_issues = validate_content_for_edit_lane(
|
|
1144
1260
|
content,
|
|
1145
1261
|
file_path,
|
|
1146
1262
|
old_content,
|
|
@@ -1170,9 +1286,9 @@ def _report_hook_blocking_issues(
|
|
|
1170
1286
|
) -> None:
|
|
1171
1287
|
"""Write a deny payload when a hook target trips a check that still guards it.
|
|
1172
1288
|
|
|
1173
|
-
The full code-rules verdict stays off hook-infrastructure files; this runs
|
|
1174
|
-
|
|
1175
|
-
|
|
1289
|
+
The full code-rules verdict stays off hook-infrastructure files; this runs
|
|
1290
|
+
the edit-lane hook-infrastructure checks and emits the deny payload when
|
|
1291
|
+
one fires.
|
|
1176
1292
|
|
|
1177
1293
|
Args:
|
|
1178
1294
|
content: The fragment or whole-file body under validation.
|
|
@@ -1187,6 +1303,7 @@ def _report_hook_blocking_issues(
|
|
|
1187
1303
|
file_path,
|
|
1188
1304
|
full_file_content_after_edit,
|
|
1189
1305
|
prior_full_file_content,
|
|
1306
|
+
phase=EDIT_LANE_PHASE,
|
|
1190
1307
|
)
|
|
1191
1308
|
if not all_blocking_issues:
|
|
1192
1309
|
return
|