claude-dev-env 1.75.0 → 1.76.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 (39) 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/docs/CODE_RULES.md +1 -1
  6. package/hooks/blocking/CLAUDE.md +2 -2
  7. package/hooks/blocking/claude_md_orphan_file_blocker.py +170 -20
  8. package/hooks/blocking/code_rules_duplicate_body.py +378 -26
  9. package/hooks/blocking/code_rules_enforcer.py +36 -5
  10. package/hooks/blocking/code_rules_imports_logging.py +679 -1
  11. package/hooks/blocking/code_rules_shared.py +8 -5
  12. package/hooks/blocking/code_rules_test_assertions.py +6 -7
  13. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +484 -0
  14. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
  15. package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +157 -0
  16. package/hooks/blocking/test_code_rules_enforcer_same_file_inline_duplicate.py +466 -0
  17. package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +11 -9
  18. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +758 -0
  19. package/hooks/blocking/test_code_rules_logging_printf_tokens.py +134 -0
  20. package/hooks/blocking/test_verification_verdict_store.py +66 -1
  21. package/hooks/blocking/test_verifier_verdict_minter.py +64 -5
  22. package/hooks/blocking/verification_verdict_store.py +19 -5
  23. package/hooks/blocking/verifier_verdict_minter.py +18 -15
  24. package/hooks/hooks_constants/blocking_check_limits.py +30 -1
  25. package/hooks/hooks_constants/claude_md_orphan_file_blocker_constants.py +52 -24
  26. package/hooks/hooks_constants/code_rules_enforcer_constants.py +41 -1
  27. package/hooks/hooks_constants/duplicate_function_body_constants.py +21 -5
  28. package/package.json +1 -1
  29. package/rules/claude-md-orphan-file.md +7 -8
  30. package/rules/docstring-prose-matches-implementation.md +1 -0
  31. package/rules/package-inventory-stale-entry.md +8 -0
  32. package/skills/anthropic-plan/CLAUDE.md +1 -1
  33. package/skills/anthropic-plan/SKILL.md +15 -2
  34. package/skills/autoconverge/workflow/converge.contract.test.mjs +12 -19
  35. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +71 -0
  36. package/skills/autoconverge/workflow/converge.mjs +86 -110
  37. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +58 -4
  38. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +9 -0
  39. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +42 -0
@@ -23,6 +23,11 @@ from bugteam_scripts_constants.bugteam_code_rules_gate_constants import (
23
23
  BANNED_NOUN_DEFINITION_LINE_GROUP_INDEX,
24
24
  BANNED_NOUN_SPAN_GROUP_INDEX,
25
25
  BANNED_NOUN_VIOLATION_PATTERN,
26
+ INLINE_DUPLICATE_BODY_ENCLOSING_LINE_GROUP_INDEX,
27
+ INLINE_DUPLICATE_BODY_ENCLOSING_SPAN_GROUP_INDEX,
28
+ INLINE_DUPLICATE_BODY_HELPER_LINE_GROUP_INDEX,
29
+ INLINE_DUPLICATE_BODY_HELPER_SPAN_GROUP_INDEX,
30
+ INLINE_DUPLICATE_BODY_VIOLATION_PATTERN,
26
31
  HUNK_HEADER_RAW_PATTERN,
27
32
  ISOLATION_DEFINITION_LINE_GROUP_INDEX,
28
33
  ISOLATION_SPAN_GROUP_INDEX,
@@ -958,6 +963,43 @@ def banned_noun_span_range(violation_text: str) -> range | None:
958
963
  return range(definition_line, definition_line + line_span)
959
964
 
960
965
 
966
+ def inline_duplicate_body_span_lines(violation_text: str) -> frozenset[int] | None:
967
+ """Return the union of both spans of a same-file inline-duplicate issue, or None.
968
+
969
+ The same-file inline-duplicate message names two functions that share a body —
970
+ the helper and the enclosing function carrying the inline copy — and the live
971
+ Write/Edit hook scopes the violation by the UNION of both spans, blocking when
972
+ an edit touches either function. So the message carries both spans: ``(inline
973
+ duplicate body spans: helper at line H spanning P lines, enclosing at line E
974
+ spanning Q lines)``. The two spans can be disjoint (an unrelated function may
975
+ sit between the helper and its inline copy), so this returns the union as a
976
+ line-number set rather than a single contiguous range — a range covering the
977
+ gap would wrongly block an edit confined to that intervening function, which
978
+ the PreToolUse path leaves unflagged.
979
+
980
+ Args:
981
+ violation_text: A single violation string emitted by the enforcer.
982
+
983
+ Returns:
984
+ The frozenset of every line in the helper span and the enclosing span, or
985
+ None when the text is not a same-file inline-duplicate violation.
986
+ """
987
+ span_match = INLINE_DUPLICATE_BODY_VIOLATION_PATTERN.search(violation_text)
988
+ if span_match is None:
989
+ return None
990
+ helper_line = int(span_match.group(INLINE_DUPLICATE_BODY_HELPER_LINE_GROUP_INDEX))
991
+ helper_span = int(span_match.group(INLINE_DUPLICATE_BODY_HELPER_SPAN_GROUP_INDEX))
992
+ enclosing_line = int(
993
+ span_match.group(INLINE_DUPLICATE_BODY_ENCLOSING_LINE_GROUP_INDEX)
994
+ )
995
+ enclosing_span = int(
996
+ span_match.group(INLINE_DUPLICATE_BODY_ENCLOSING_SPAN_GROUP_INDEX)
997
+ )
998
+ helper_lines = range(helper_line, helper_line + helper_span)
999
+ enclosing_lines = range(enclosing_line, enclosing_line + enclosing_span)
1000
+ return frozenset(helper_lines) | frozenset(enclosing_lines)
1001
+
1002
+
961
1003
  def _all_span_range_extractors() -> tuple[Callable[[str], range | None], ...]:
962
1004
  return (
963
1005
  function_length_span_range,
@@ -1003,10 +1045,15 @@ def split_violations_by_scope(
1003
1045
 
1004
1046
  Returns:
1005
1047
  Tuple ``(blocking, advisory)``. When *all_added_line_numbers* is
1006
- None, every issue is blocking. Every diff-scoped violation
1007
- (function-length, HOME/TMP isolation, banned-noun) carries an
1008
- enclosing-unit span fragment that ``enclosing_span_range`` reconstructs
1009
- through one shared extractor registry; such a violation is blocking
1048
+ None, every issue is blocking. A same-file inline-duplicate violation
1049
+ carries both the helper span and the enclosing span;
1050
+ ``inline_duplicate_body_span_lines`` reconstructs their union as a
1051
+ line-number set, and the violation is blocking when an added line falls
1052
+ in either span — matching the live Write/Edit hook's union scoping. Every
1053
+ other diff-scoped violation (function-length, HOME/TMP isolation,
1054
+ banned-noun) carries one enclosing-unit span fragment that
1055
+ ``enclosing_span_range`` reconstructs through one shared extractor
1056
+ registry; such a violation is blocking
1010
1057
  when its declared span intersects the added lines (the unit grew or its
1011
1058
  signature changed in this diff) and advisory otherwise (a pre-existing
1012
1059
  untouched unit). Every other issue is blocking when its ``Line N:``
@@ -1017,6 +1064,13 @@ def split_violations_by_scope(
1017
1064
  blocking: list[str] = []
1018
1065
  advisory: list[str] = []
1019
1066
  for each_issue in all_issues:
1067
+ inline_duplicate_lines = inline_duplicate_body_span_lines(each_issue)
1068
+ if inline_duplicate_lines is not None:
1069
+ if inline_duplicate_lines & all_added_line_numbers:
1070
+ blocking.append(each_issue)
1071
+ else:
1072
+ advisory.append(each_issue)
1073
+ continue
1020
1074
  span_range = enclosing_span_range(each_issue)
1021
1075
  if span_range is not None:
1022
1076
  if any(each_line in all_added_line_numbers for each_line in span_range):
@@ -31,6 +31,15 @@ BANNED_NOUN_VIOLATION_PATTERN: re.Pattern[str] = re.compile(
31
31
  BANNED_NOUN_DEFINITION_LINE_GROUP_INDEX: int = 1
32
32
  BANNED_NOUN_SPAN_GROUP_INDEX: int = 2
33
33
 
34
+ INLINE_DUPLICATE_BODY_VIOLATION_PATTERN: re.Pattern[str] = re.compile(
35
+ r"\(inline duplicate body spans: helper at line (\d+) spanning (\d+) lines, "
36
+ r"enclosing at line (\d+) spanning (\d+) lines\)"
37
+ )
38
+ INLINE_DUPLICATE_BODY_HELPER_LINE_GROUP_INDEX: int = 1
39
+ INLINE_DUPLICATE_BODY_HELPER_SPAN_GROUP_INDEX: int = 2
40
+ INLINE_DUPLICATE_BODY_ENCLOSING_LINE_GROUP_INDEX: int = 3
41
+ INLINE_DUPLICATE_BODY_ENCLOSING_SPAN_GROUP_INDEX: int = 4
42
+
34
43
  ALL_CODE_FILE_EXTENSIONS: frozenset[str] = frozenset(
35
44
  {".py", ".js", ".ts", ".tsx", ".jsx"}
36
45
  )
@@ -1022,3 +1022,45 @@ def test_main_staged_mode_passes_on_staged_deletion_of_clean_file(
1022
1022
  "a staged deletion has no staged blob; the gate must skip it cleanly "
1023
1023
  "rather than fail closed as if the file were unreadable"
1024
1024
  )
1025
+
1026
+
1027
+ _INLINE_DUPLICATE_MESSAGE = (
1028
+ "Function '_wait_for_render' duplicates an inline block in '_navigate_then_wait'"
1029
+ " — this function body is also present inline (Reuse before create / DRY) "
1030
+ "(inline duplicate body spans: helper at line 4 spanning 10 lines, "
1031
+ "enclosing at line 16 spanning 11 lines)"
1032
+ )
1033
+
1034
+
1035
+ def test_inline_duplicate_body_span_lines_unions_helper_and_enclosing_spans() -> None:
1036
+ """The same-file inline-duplicate message carries both spans, and the gate
1037
+ recovers their union as a line-number set so a touch of either function blocks —
1038
+ mirroring the live Write/Edit hook's union scoping."""
1039
+ span_lines = gate_module.inline_duplicate_body_span_lines(_INLINE_DUPLICATE_MESSAGE)
1040
+ assert span_lines == frozenset(range(4, 14)) | frozenset(range(16, 27))
1041
+
1042
+
1043
+ def test_inline_duplicate_blocks_when_only_enclosing_copy_added() -> None:
1044
+ """An added line in the enclosing span alone blocks, because the live hook scopes
1045
+ by the union of both spans and blocks the same edit — the common shape where a
1046
+ block is copied INTO a growing enclosing function, leaving the helper untouched."""
1047
+ added_line_in_enclosing_only = 18
1048
+ blocking, advisory = gate_module.split_violations_by_scope(
1049
+ [_INLINE_DUPLICATE_MESSAGE],
1050
+ all_added_line_numbers={added_line_in_enclosing_only},
1051
+ )
1052
+ assert blocking == [_INLINE_DUPLICATE_MESSAGE]
1053
+ assert advisory == []
1054
+
1055
+
1056
+ def test_inline_duplicate_advises_when_gap_line_added() -> None:
1057
+ """An edit confined to the gap between the helper span (4-13) and the enclosing
1058
+ span (16-26) must not block, matching the live hook. The union set keeps the gap
1059
+ out of scope where a single contiguous range would wrongly block it."""
1060
+ gap_line_between_spans = 14
1061
+ blocking, advisory = gate_module.split_violations_by_scope(
1062
+ [_INLINE_DUPLICATE_MESSAGE],
1063
+ all_added_line_numbers={gap_line_between_spans},
1064
+ )
1065
+ assert advisory == [_INLINE_DUPLICATE_MESSAGE]
1066
+ assert blocking == []