claude-dev-env 1.75.0 → 1.77.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 (47) hide show
  1. package/_shared/pr-loop/scripts/code_rules_gate.py +60 -5
  2. package/_shared/pr-loop/scripts/pr_loop_shared_constants/CLAUDE.md +1 -0
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/inline_duplicate_body_span_constants.py +22 -0
  4. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +147 -3
  5. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -0
  6. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +8 -4
  7. package/docs/CODE_RULES.md +1 -1
  8. package/hooks/blocking/CLAUDE.md +2 -2
  9. package/hooks/blocking/claude_md_orphan_file_blocker.py +170 -20
  10. package/hooks/blocking/code_rules_docstrings.py +378 -0
  11. package/hooks/blocking/code_rules_duplicate_body.py +378 -26
  12. package/hooks/blocking/code_rules_enforcer.py +48 -5
  13. package/hooks/blocking/code_rules_imports_logging.py +679 -1
  14. package/hooks/blocking/code_rules_shared.py +8 -5
  15. package/hooks/blocking/code_rules_test_assertions.py +6 -7
  16. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +484 -0
  17. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
  18. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +174 -0
  19. package/hooks/blocking/test_code_rules_enforcer_docstring_cardinal_family.py +176 -0
  20. package/hooks/blocking/test_code_rules_enforcer_docstring_runon_sentence.py +267 -0
  21. package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +157 -0
  22. package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +466 -0
  23. package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +11 -9
  24. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +758 -0
  25. package/hooks/blocking/test_code_rules_logging_printf_tokens.py +134 -0
  26. package/hooks/blocking/test_verification_verdict_store.py +66 -1
  27. package/hooks/blocking/test_verifier_verdict_minter.py +64 -5
  28. package/hooks/blocking/verification_verdict_store.py +19 -5
  29. package/hooks/blocking/verifier_verdict_minter.py +18 -15
  30. package/hooks/hooks_constants/blocking_check_limits.py +56 -0
  31. package/hooks/hooks_constants/claude_md_orphan_file_blocker_constants.py +52 -24
  32. package/hooks/hooks_constants/code_rules_enforcer_constants.py +76 -1
  33. package/hooks/hooks_constants/duplicate_function_body_constants.py +21 -5
  34. package/package.json +1 -1
  35. package/rules/CLAUDE.md +1 -0
  36. package/rules/claude-md-orphan-file.md +7 -8
  37. package/rules/docstring-prose-matches-implementation.md +5 -1
  38. package/rules/package-inventory-stale-entry.md +16 -0
  39. package/rules/plain-illustrative-docstrings.md +56 -0
  40. package/skills/anthropic-plan/CLAUDE.md +1 -1
  41. package/skills/anthropic-plan/SKILL.md +15 -2
  42. package/skills/autoconverge/workflow/converge.contract.test.mjs +12 -19
  43. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +71 -0
  44. package/skills/autoconverge/workflow/converge.mjs +86 -110
  45. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +58 -4
  46. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +9 -0
  47. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +42 -0
@@ -1,33 +1,44 @@
1
- """Cross-file duplicate top-level function body detection.
1
+ """Duplicate top-level function body detection.
2
2
 
3
- The check flags a top-level function in the file being written whose body is
4
- structurally identical to a top-level function already defined in a sibling
5
- ``.py`` module in the same directory. This catches the Reuse-before-create / DRY
6
- violation where a helper is copy-pasted across several modules instead of being
7
- imported from one shared home.
3
+ ``check_duplicate_function_body_across_files`` flags a top-level function in the
4
+ file being written whose body is structurally identical to a top-level function
5
+ already defined in a sibling ``.py`` module in the same directory.
6
+ ``check_same_file_inline_duplicate_body`` flags a top-level function whose body
7
+ appears verbatim as a contiguous statement block inside another function in the
8
+ same file — the inlined-block copy the cross-file whole-function comparison
9
+ misses. Both catch the Reuse-before-create / DRY violation where a block of logic
10
+ is copied instead of called from one shared home, so a fix that lands in one copy
11
+ leaves the other carrying the bug.
8
12
 
9
- The scan is deliberately conservative to keep false positives near zero:
13
+ The scans are deliberately conservative to keep false positives near zero:
10
14
 
11
15
  - Only module-scope ``def`` / ``async def`` bodies are compared (the copied-helper
12
16
  case), never methods nested in a class.
13
- - Bodies are compared by their normalized AST structure with the leading
14
- docstring dropped, so reformatting and comment differences do not hide a copy.
15
- The comparison keeps identifier names, so a match requires the body statements,
16
- including local variable names, to be structurally identical; it does not
17
- consider the parameter list, decorators, or whether the function is ``async``.
18
- - A body must contain at least ``MINIMUM_DUPLICATE_BODY_STATEMENTS`` statements;
19
- trivial one- or two-line helpers (``return None``, a single delegation) are too
20
- common to flag.
17
+ - The cross-file scan compares whole bodies by their normalized AST structure
18
+ with the leading docstring dropped, keeping identifier names, so a match
19
+ requires the body statements local variable names included to be
20
+ structurally identical; it ignores the parameter list, decorators, and whether
21
+ the function is ``async``, and a body must hold at least
22
+ ``MINIMUM_DUPLICATE_BODY_STATEMENTS`` statements.
23
+ - The same-file inline scan trims a helper's leading ``assert`` preconditions and
24
+ drops its docstring, then seeks the remaining window verbatim inside another
25
+ function's reachable statement blocks. String-literal constants are canonicalized
26
+ before comparison, so two blocks that differ only in a logging or error message
27
+ still collide, while the window must hold a substantial compound statement
28
+ (``try``, ``for``, ``while``, ``with``) and sit inside a strictly larger,
29
+ non-twin enclosing body. A bare ``if`` guard, a flat run, and a structural-twin
30
+ peer helper never flag.
21
31
  - Test files and ``__init__.py`` re-export surfaces never participate, on either
22
32
  the writing side or the sibling side.
23
33
 
24
- Unlike most code-rules checks, this one runs on hook-infrastructure files: the
25
- copied-helper violation it targets appears most often in the ``blocking/`` hook
26
- directory itself, so gating it behind the hook-infrastructure exemption would
34
+ Unlike most code-rules checks, these run on hook-infrastructure files: the
35
+ copied-block violation they target appears often in the ``blocking/`` hook
36
+ directory itself, so gating them behind the hook-infrastructure exemption would
27
37
  leave the exact violation class unguarded. The enforcer entry points route a
28
- hook ``.py`` target to this single check even though the full code-rules verdict
29
- stays off hook infrastructure, so a Write or pre-check against a file under the
30
- ``blocking/`` directory still blocks a copied sibling helper.
38
+ hook ``.py`` target to both checks even though the full code-rules verdict stays
39
+ off hook infrastructure, so a Write or pre-check against a file under the
40
+ ``blocking/`` directory still blocks a copied sibling helper and an inlined
41
+ helper body.
31
42
 
32
43
  ``advise_cross_skill_duplicate_helper`` is the non-blocking companion for a
33
44
  different layout: a helper copied between two skills' ``scripts`` directories.
@@ -40,6 +51,7 @@ folders; within one skill the blocking check above already covers the copy.
40
51
  """
41
52
 
42
53
  import ast
54
+ import copy
43
55
  import sys
44
56
  from pathlib import Path
45
57
 
@@ -63,12 +75,39 @@ from hooks_constants.duplicate_function_body_constants import ( # noqa: E402
63
75
  MAX_CROSS_SKILL_ADVISORY_ISSUES,
64
76
  MAX_DUPLICATE_BODY_ISSUES,
65
77
  MINIMUM_DUPLICATE_BODY_STATEMENTS,
78
+ MINIMUM_INLINE_DUPLICATE_BODY_STATEMENTS,
66
79
  PYTHON_SOURCE_SUFFIX,
80
+ SAME_FILE_INLINE_DUPLICATE_GUIDANCE,
81
+ SAME_FILE_INLINE_DUPLICATE_SPAN_SUFFIX_TEMPLATE,
67
82
  SKILL_SCRIPTS_DIRECTORY_NAME,
68
83
  SKILLS_DIRECTORY_NAME,
69
84
  )
70
85
 
71
86
 
87
+ def _body_statements_without_docstring(
88
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
89
+ ) -> list[ast.stmt]:
90
+ """Return the function's top-level body statements with a leading docstring dropped.
91
+
92
+ A function whose first statement is a bare string-literal expression carries a
93
+ docstring; that statement is omitted so two copies that differ only in their
94
+ docstring compare equal. A function with no leading docstring returns its body
95
+ unchanged.
96
+
97
+ Args:
98
+ function_node: The module-scope function whose body to read.
99
+
100
+ Returns:
101
+ The top-level body statements, excluding a leading docstring expression.
102
+ """
103
+ body_statements = list(function_node.body)
104
+ if body_statements and isinstance(body_statements[0], ast.Expr):
105
+ first_value = body_statements[0].value
106
+ if isinstance(first_value, ast.Constant) and isinstance(first_value.value, str):
107
+ return body_statements[1:]
108
+ return body_statements
109
+
110
+
72
111
  def _normalized_body_signature(function_node: ast.FunctionDef | ast.AsyncFunctionDef) -> str | None:
73
112
  """Return a position-independent structural fingerprint of the function body.
74
113
 
@@ -84,11 +123,7 @@ def _normalized_body_signature(function_node: ast.FunctionDef | ast.AsyncFunctio
84
123
  A normalized AST dump of the body statements, or None when the body is
85
124
  too small to compare.
86
125
  """
87
- body_statements = list(function_node.body)
88
- if body_statements and isinstance(body_statements[0], ast.Expr):
89
- first_value = body_statements[0].value
90
- if isinstance(first_value, ast.Constant) and isinstance(first_value.value, str):
91
- body_statements = body_statements[1:]
126
+ body_statements = _body_statements_without_docstring(function_node)
92
127
  if len(body_statements) < MINIMUM_DUPLICATE_BODY_STATEMENTS:
93
128
  return None
94
129
  return "\n".join(
@@ -301,6 +336,323 @@ def check_duplicate_function_body_across_files(
301
336
  )
302
337
 
303
338
 
339
+ class _StringConstantCanonicalizer(ast.NodeTransformer):
340
+ """Rewrite every string-literal constant to one placeholder.
341
+
342
+ Two copied blocks most often differ only in a message string — a logging or
343
+ error suffix the author tweaked while leaving the call, selector, and control
344
+ structure identical. Canonicalizing string constants before dumping lets such
345
+ blocks collide while a different call target, selector, or numeric constant
346
+ still keeps two blocks distinct. Non-string constants (numbers, ``None``,
347
+ booleans) are left untouched so a genuine value difference stays visible.
348
+ """
349
+
350
+ def visit_Constant(self, node: ast.Constant) -> ast.Constant:
351
+ """Replace a string-valued constant with a fixed placeholder string.
352
+
353
+ Args:
354
+ node: The constant node under transformation.
355
+
356
+ Returns:
357
+ A placeholder constant for a string value, or the node unchanged for
358
+ any non-string constant.
359
+ """
360
+ if isinstance(node.value, str):
361
+ return ast.copy_location(ast.Constant(value=""), node)
362
+ return node
363
+
364
+
365
+ def _normalized_statement_dump(statement: ast.stmt) -> str:
366
+ """Return the normalized AST dump of one statement.
367
+
368
+ Canonicalizes every string-literal constant to one placeholder before
369
+ dumping, so two statements that differ only in a message string still produce
370
+ the same dump. The call structure, selectors, exception handlers, and numeric
371
+ constants are preserved, so a genuine logic difference still keeps two
372
+ statements distinct.
373
+
374
+ Args:
375
+ statement: The statement node to fingerprint.
376
+
377
+ Returns:
378
+ The annotate-fields-suppressed AST dump of the statement after string
379
+ constants are canonicalized.
380
+ """
381
+ canonical_statement = _StringConstantCanonicalizer().visit(
382
+ copy.deepcopy(statement)
383
+ )
384
+ return ast.dump(canonical_statement, annotate_fields=False)
385
+
386
+
387
+ def _statement_blocks_in_function(
388
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
389
+ ) -> list[list[ast.stmt]]:
390
+ """Return every statement list reachable inside a function body.
391
+
392
+ A copied helper body can sit at the function's top level or nested inside a
393
+ branch, loop, or context block, so the inline-duplicate scan walks each nested
394
+ statement list as its own window source. The function's own immediate body is
395
+ the first block; every nested block (``If`` arms, ``For``/``While`` bodies,
396
+ ``With``/``Try`` bodies and handlers) follows.
397
+
398
+ Args:
399
+ function_node: The module-scope function whose blocks to collect.
400
+
401
+ Returns:
402
+ A list of statement lists, one per reachable block in the function.
403
+ """
404
+ all_blocks: list[list[ast.stmt]] = []
405
+ for each_node in ast.walk(function_node):
406
+ for each_field_name in ("body", "orelse", "finalbody"):
407
+ block = getattr(each_node, each_field_name, None)
408
+ if isinstance(block, list) and block and isinstance(block[0], ast.stmt):
409
+ all_blocks.append(block)
410
+ for each_handler in getattr(each_node, "handlers", []) or []:
411
+ if isinstance(each_handler, ast.ExceptHandler):
412
+ all_blocks.append(each_handler.body)
413
+ return all_blocks
414
+
415
+
416
+ def _total_reachable_statement_count(
417
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
418
+ ) -> int:
419
+ """Return the count of every statement reachable inside the function body.
420
+
421
+ Walks the function's immediate body and every nested block — ``If`` arms,
422
+ loop and context bodies, ``Try`` bodies and handlers and ``finalbody`` — so a
423
+ duplicated window wrapped inside a single top-level compound (a ``try``/
424
+ ``finally`` cleanup or one ``if`` guard) still counts the statements the window
425
+ occupies plus the statements around it. The leading docstring is excluded from
426
+ the immediate body so a docstring does not inflate the count.
427
+
428
+ Args:
429
+ function_node: The function whose reachable statements to count.
430
+
431
+ Returns:
432
+ The number of statements reachable in the function, excluding a leading
433
+ docstring expression.
434
+ """
435
+ has_leading_docstring = len(_body_statements_without_docstring(function_node)) < len(
436
+ function_node.body
437
+ )
438
+ total_statement_count = sum(
439
+ 1 for each_node in ast.walk(function_node) if isinstance(each_node, ast.stmt)
440
+ )
441
+ if has_leading_docstring:
442
+ return total_statement_count - 1
443
+ return total_statement_count
444
+
445
+
446
+ def _normalized_body_dump_multiset(
447
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
448
+ ) -> list[str]:
449
+ """Return the function's docstring-stripped statement dumps, sorted.
450
+
451
+ Used to test whether two functions are structural twins — same statements in
452
+ any arrangement under string normalization. Sorting makes the comparison
453
+ order-independent, so two peer helpers that read different inputs but share
454
+ the same statement shapes compare equal and are left to the cross-file check.
455
+
456
+ Args:
457
+ function_node: The function whose body to fingerprint.
458
+
459
+ Returns:
460
+ The sorted per-statement normalized dumps of the docstring-stripped body.
461
+ """
462
+ body_statements = _body_statements_without_docstring(function_node)
463
+ return sorted(_normalized_statement_dump(each) for each in body_statements)
464
+
465
+
466
+ def _function_inlines_window(
467
+ helper_node: ast.FunctionDef | ast.AsyncFunctionDef,
468
+ enclosing_node: ast.FunctionDef | ast.AsyncFunctionDef,
469
+ all_helper_window_dumps: list[str],
470
+ ) -> bool:
471
+ """Return whether a function inlines a helper window inside a larger body.
472
+
473
+ Slides a window the length of ``all_helper_window_dumps`` over each reachable
474
+ statement block in the enclosing function; a window whose per-statement dumps
475
+ match the helper's, in order, is the inlined copy. Two guards keep the report
476
+ to the genuine "helper inlined inside a larger function" shape:
477
+
478
+ - The enclosing function carries more total reachable statements than the
479
+ window, so a copy that fills a whole peer function is not reported here. The
480
+ count spans every nested block, so a window wrapped inside one top-level
481
+ compound — a ``try``/``finally`` cleanup, an ``except`` handler, or a single
482
+ ``if`` guard — still clears the guard and is scanned.
483
+ - The two functions are not structural twins — their docstring-stripped
484
+ statement multisets differ — so two peer helpers that share a statement
485
+ shape but read different inputs are left to the cross-file whole-function
486
+ check ``check_duplicate_function_body_across_files`` rather than reported as
487
+ an inline duplicate.
488
+
489
+ Args:
490
+ helper_node: The candidate helper whose window is sought.
491
+ enclosing_node: The candidate enclosing function to scan.
492
+ all_helper_window_dumps: The helper's substantive-block per-statement dumps.
493
+
494
+ Returns:
495
+ True when some block in the enclosing function contains the helper window
496
+ verbatim as a contiguous run inside a strictly larger, non-twin body.
497
+ """
498
+ window_length = len(all_helper_window_dumps)
499
+ if _total_reachable_statement_count(enclosing_node) <= window_length:
500
+ return False
501
+ if _normalized_body_dump_multiset(helper_node) == _normalized_body_dump_multiset(
502
+ enclosing_node
503
+ ):
504
+ return False
505
+ for each_block in _statement_blocks_in_function(enclosing_node):
506
+ if len(each_block) < window_length:
507
+ continue
508
+ block_dumps = [_normalized_statement_dump(each) for each in each_block]
509
+ for each_start_index in range(len(block_dumps) - window_length + 1):
510
+ window = block_dumps[each_start_index : each_start_index + window_length]
511
+ if window == all_helper_window_dumps:
512
+ return True
513
+ return False
514
+
515
+
516
+ def _helper_match_window_dumps(
517
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
518
+ ) -> list[str] | None:
519
+ """Return the helper's substantive block as per-statement dumps, or None.
520
+
521
+ Drops a leading docstring the same way ``_normalized_body_signature`` does,
522
+ then trims leading ``assert`` precondition guards so the match window begins at
523
+ the helper's first non-assert statement. A helper commonly wraps a copied block
524
+ in its own ``assert`` precondition, so trimming those lets the helper's
525
+ substantive block match the same block inlined elsewhere without the guard.
526
+ Only ``assert`` is trimmed — a leading assignment or call carries data the
527
+ duplicate must share, so trimming it would expose a generic tail and match
528
+ unrelated peer helpers.
529
+
530
+ The window must hold at least ``MINIMUM_INLINE_DUPLICATE_BODY_STATEMENTS``
531
+ statements and must contain a substantial compound statement — a ``try``,
532
+ ``for``, ``while``, or ``with`` block. A run of flat statements, and a bare
533
+ ``if`` guard (an idiomatic strict-vs-optional validator pair where one wraps a
534
+ ``None`` check around the other's ``if ...: raise`` narrow), are too common to
535
+ be a meaningful inline duplicate and never flag, while a duplicated
536
+ ``try``/``except`` or loop block — the substantial control structure worth a
537
+ shared helper — does.
538
+
539
+ Args:
540
+ function_node: The module-scope function to treat as a candidate helper.
541
+
542
+ Returns:
543
+ The per-statement normalized dumps of the helper's substantive block, or
544
+ None when it has no compound statement or is shorter than the minimum.
545
+ """
546
+ body_statements = _body_statements_without_docstring(function_node)
547
+ first_non_assert_index = next(
548
+ (
549
+ each_index
550
+ for each_index, each_statement in enumerate(body_statements)
551
+ if not isinstance(each_statement, ast.Assert)
552
+ ),
553
+ None,
554
+ )
555
+ if first_non_assert_index is None:
556
+ return None
557
+ match_window = body_statements[first_non_assert_index:]
558
+ if len(match_window) < MINIMUM_INLINE_DUPLICATE_BODY_STATEMENTS:
559
+ return None
560
+ substantial_compound_statement_types = (ast.Try, ast.For, ast.While, ast.With)
561
+ has_substantial_compound = any(
562
+ isinstance(each, substantial_compound_statement_types)
563
+ for each in match_window
564
+ )
565
+ if not has_substantial_compound:
566
+ return None
567
+ return [_normalized_statement_dump(each) for each in match_window]
568
+
569
+
570
+ def check_same_file_inline_duplicate_body(
571
+ content: str,
572
+ file_path: str,
573
+ all_changed_lines: set[int] | None = None,
574
+ defer_scope_to_caller: bool = False,
575
+ ) -> list[str]:
576
+ """Flag a module-scope helper whose body is inlined inside another function.
577
+
578
+ Compares each module-scope function's body against every other module-scope
579
+ function in the same file, reporting any helper whose body appears verbatim as
580
+ a contiguous statement block inside another function. This is the same-file
581
+ counterpart to ``check_duplicate_function_body_across_files``, which only
582
+ compares whole functions across sibling modules and so misses a helper that
583
+ duplicates a block already inlined in a same-file function.
584
+
585
+ Violations are span-scoped to the lines an edit touched the same way the
586
+ cross-file check scopes its own: a violation blocks when either the helper's
587
+ span or the enclosing function's span intersects the changed lines, so an
588
+ unrelated edit to a file that already carries the duplication does not block,
589
+ while a Write or an edit touching either function still flags.
590
+
591
+ Args:
592
+ content: The full post-edit file content being written.
593
+ file_path: The destination path of the write.
594
+ all_changed_lines: Post-edit line numbers the current edit touched, or
595
+ None to treat the whole file as in scope. When provided, a violation
596
+ blocks only when the helper's span or the enclosing function's span
597
+ intersects the changed lines.
598
+ defer_scope_to_caller: When True, return every violation so the commit/push
599
+ gate's ``split_violations_by_scope`` can scope by added line.
600
+
601
+ Returns:
602
+ Human-readable violation strings, one per inlined-duplicate helper, scoped
603
+ to the changed lines unless *defer_scope_to_caller* is True or
604
+ *all_changed_lines* is None.
605
+ """
606
+ if Path(file_path).name == DUNDER_INIT_FILENAME:
607
+ return []
608
+ if is_test_file(file_path):
609
+ return []
610
+ try:
611
+ tree = ast.parse(content)
612
+ except SyntaxError:
613
+ return []
614
+ all_top_level_functions = [
615
+ each_node
616
+ for each_node in tree.body
617
+ if isinstance(each_node, ast.FunctionDef | ast.AsyncFunctionDef)
618
+ ]
619
+ all_violations_in_walk_order: list[tuple[frozenset[int], str]] = []
620
+ for each_helper in all_top_level_functions:
621
+ helper_window_dumps = _helper_match_window_dumps(each_helper)
622
+ if helper_window_dumps is None:
623
+ continue
624
+ for each_enclosing in all_top_level_functions:
625
+ if each_enclosing is each_helper:
626
+ continue
627
+ if not _function_inlines_window(
628
+ each_helper, each_enclosing, helper_window_dumps
629
+ ):
630
+ continue
631
+ helper_span = _function_definition_span(each_helper)
632
+ enclosing_span = _function_definition_span(each_enclosing)
633
+ in_scope_lines = frozenset(helper_span) | frozenset(enclosing_span)
634
+ span_suffix = SAME_FILE_INLINE_DUPLICATE_SPAN_SUFFIX_TEMPLATE.format(
635
+ helper_start=helper_span.start,
636
+ helper_length=len(helper_span),
637
+ enclosing_start=enclosing_span.start,
638
+ enclosing_length=len(enclosing_span),
639
+ )
640
+ message = (
641
+ f"Function {each_helper.name!r} duplicates an inline block in "
642
+ f"{each_enclosing.name!r} — {SAME_FILE_INLINE_DUPLICATE_GUIDANCE} "
643
+ f"{span_suffix}"
644
+ )
645
+ all_violations_in_walk_order.append((in_scope_lines, message))
646
+ break
647
+ if len(all_violations_in_walk_order) >= MAX_DUPLICATE_BODY_ISSUES:
648
+ break
649
+ return _scope_violations_to_changed_lines(
650
+ all_violations_in_walk_order,
651
+ all_changed_lines,
652
+ defer_scope_to_caller,
653
+ )
654
+
655
+
304
656
  def _skill_scripts_root(file_path: str) -> Path | None:
305
657
  """Return the ``skills/<name>/scripts`` root the written file sits under.
306
658
 
@@ -67,12 +67,15 @@ from code_rules_dead_module_constant import ( # noqa: E402
67
67
  from code_rules_docstrings import ( # noqa: E402
68
68
  check_class_docstring_names_public_methods,
69
69
  check_docstring_args_match_signature,
70
+ check_docstring_args_single_line_scope_vs_span,
71
+ check_docstring_cardinal_count_matches_constant_family,
70
72
  check_docstring_fallback_branch_coverage,
71
73
  check_docstring_format,
72
74
  check_docstring_names_undefined_constant,
73
75
  check_docstring_no_consumer_claim,
74
76
  check_docstring_no_inline_literal_claim,
75
77
  check_docstring_returns_plural_cardinality,
78
+ check_docstring_runon_sentence,
76
79
  check_docstring_step_enumeration_dispatch_coverage,
77
80
  check_docstring_tuple_enumeration_match,
78
81
  check_docstring_unguarded_malformed_payload_claim,
@@ -81,13 +84,17 @@ from code_rules_docstrings import ( # noqa: E402
81
84
  from code_rules_duplicate_body import ( # noqa: E402
82
85
  advise_cross_skill_duplicate_helper,
83
86
  check_duplicate_function_body_across_files,
87
+ check_same_file_inline_duplicate_body,
84
88
  )
85
89
  from code_rules_imports_logging import ( # noqa: E402
86
90
  advise_file_line_count,
87
91
  check_e2e_test_naming,
92
+ check_import_block_sorted,
88
93
  check_imports_at_top,
94
+ check_js_resume_task_enumeration_coverage,
89
95
  check_library_print,
90
96
  check_logging_fstrings,
97
+ check_logging_printf_tokens,
91
98
  check_windows_api_none,
92
99
  )
93
100
  from code_rules_magic_values import ( # noqa: E402
@@ -225,7 +232,16 @@ def validate_content(
225
232
  if not is_test_file(file_path):
226
233
  all_issues.extend(check_comment_changes(old_content, content, file_path))
227
234
  all_issues.extend(check_imports_at_top(content))
235
+ all_issues.extend(
236
+ check_import_block_sorted(
237
+ effective_content,
238
+ file_path,
239
+ all_changed_lines,
240
+ defer_scope_to_caller,
241
+ )
242
+ )
228
243
  all_issues.extend(check_logging_fstrings(content))
244
+ all_issues.extend(check_logging_printf_tokens(content, file_path))
229
245
  all_issues.extend(check_windows_api_none(content))
230
246
  all_issues.extend(check_magic_values(content, file_path))
231
247
  all_issues.extend(check_fstring_structural_literals(content, file_path))
@@ -241,6 +257,14 @@ def validate_content(
241
257
  sibling_directory,
242
258
  )
243
259
  )
260
+ all_issues.extend(
261
+ check_same_file_inline_duplicate_body(
262
+ effective_content,
263
+ file_path,
264
+ all_changed_lines,
265
+ defer_scope_to_caller,
266
+ )
267
+ )
244
268
  all_issues.extend(check_type_escape_hatches(effective_content, file_path))
245
269
  all_issues.extend(check_banned_identifiers(content, file_path))
246
270
  all_issues.extend(
@@ -274,6 +298,7 @@ def validate_content(
274
298
  all_issues.extend(
275
299
  check_class_docstring_names_public_methods(effective_content, file_path)
276
300
  )
301
+ all_issues.extend(check_docstring_runon_sentence(effective_content, file_path))
277
302
  all_issues.extend(
278
303
  check_module_docstring_names_public_checks(effective_content, file_path)
279
304
  )
@@ -288,9 +313,17 @@ def validate_content(
288
313
  all_issues.extend(
289
314
  check_docstring_returns_plural_cardinality(effective_content, file_path)
290
315
  )
316
+ all_issues.extend(
317
+ check_docstring_cardinal_count_matches_constant_family(
318
+ effective_content, file_path
319
+ )
320
+ )
291
321
  all_issues.extend(
292
322
  check_docstring_names_undefined_constant(effective_content, file_path)
293
323
  )
324
+ all_issues.extend(
325
+ check_docstring_args_single_line_scope_vs_span(effective_content, file_path)
326
+ )
294
327
  all_issues.extend(
295
328
  check_boolean_naming(
296
329
  effective_content,
@@ -368,6 +401,9 @@ def validate_content(
368
401
  if not is_test_file(file_path):
369
402
  all_issues.extend(check_comment_changes(old_content, content, file_path))
370
403
  all_issues.extend(check_e2e_test_naming(content, file_path))
404
+ all_issues.extend(
405
+ check_js_resume_task_enumeration_coverage(content, file_path)
406
+ )
371
407
 
372
408
  if extension in ALL_CODE_EXTENSIONS:
373
409
  advise_file_line_count(content, file_path)
@@ -472,11 +508,11 @@ def _hook_infrastructure_blocking_issues(
472
508
  """Run the checks that still guard a hook Python target.
473
509
 
474
510
  The whole code-rules verdict stays off hook-infrastructure files, so this
475
- runs the two checks that must still guard them: the cross-file duplicate-body
476
- check, span-scoped to the lines an edit touched exactly as ``validate_content``
477
- scopes it for production code; and the zero-payload alias check, whose
478
- docstring names hook modules as its motivating case, run over the whole
479
- post-edit file.
511
+ runs the checks that must still guard them: the cross-file duplicate-body
512
+ check and the same-file inline-duplicate-body check, each span-scoped to the
513
+ lines an edit touched exactly as ``validate_content`` scopes it for production
514
+ code; and the zero-payload alias check, whose docstring names hook modules as
515
+ its motivating case, run over the whole post-edit file.
480
516
 
481
517
  Args:
482
518
  content: The fragment or whole-file body under validation.
@@ -501,6 +537,13 @@ def _hook_infrastructure_blocking_issues(
501
537
  file_path,
502
538
  all_changed_lines,
503
539
  )
540
+ all_issues.extend(
541
+ check_same_file_inline_duplicate_body(
542
+ effective_content,
543
+ file_path,
544
+ all_changed_lines,
545
+ )
546
+ )
504
547
  all_issues.extend(check_zero_payload_function_alias(effective_content, file_path))
505
548
  return all_issues
506
549