claude-dev-env 1.57.2 → 1.59.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 (77) hide show
  1. package/CLAUDE.md +2 -2
  2. package/_shared/pr-loop/scripts/code_rules_gate.py +36 -3
  3. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +6 -0
  4. package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
  5. package/_shared/pr-loop/scripts/reviews_disabled.py +12 -0
  6. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +265 -0
  7. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +29 -0
  8. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
  9. package/bin/install.mjs +317 -54
  10. package/bin/install.test.mjs +478 -3
  11. package/docs/CODE_RULES.md +3 -3
  12. package/hooks/blocking/code_rules_annotations_length.py +153 -0
  13. package/hooks/blocking/code_rules_dead_dataclass_field.py +319 -0
  14. package/hooks/blocking/code_rules_duplicate_body.py +287 -0
  15. package/hooks/blocking/code_rules_enforcer.py +175 -21
  16. package/hooks/blocking/code_rules_magic_values.py +98 -0
  17. package/hooks/blocking/code_rules_shared.py +41 -0
  18. package/hooks/blocking/destructive_command_blocker.py +1027 -12
  19. package/hooks/blocking/hook_prose_detector_consistency.py +150 -0
  20. package/hooks/blocking/intent_only_ending_blocker.py +155 -0
  21. package/hooks/blocking/session_handoff_blocker.py +190 -0
  22. package/hooks/blocking/subprocess_budget_completeness.py +380 -0
  23. package/hooks/blocking/test_code_rules_enforcer_annotations.py +225 -0
  24. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
  25. package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +467 -0
  26. package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +330 -0
  27. package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +179 -0
  28. package/hooks/blocking/test_code_rules_enforcer_magic_slice_bounds.py +133 -0
  29. package/hooks/blocking/test_destructive_command_blocker.py +622 -3
  30. package/hooks/blocking/test_hook_prose_detector_consistency.py +265 -0
  31. package/hooks/blocking/test_intent_only_ending_blocker.py +175 -0
  32. package/hooks/blocking/test_session_handoff_blocker.py +312 -0
  33. package/hooks/blocking/test_subprocess_budget_completeness.py +588 -0
  34. package/hooks/blocking/test_workflow_substitution_slot_blocker.py +242 -0
  35. package/hooks/blocking/workflow_substitution_slot_blocker.py +159 -0
  36. package/hooks/hooks.json +25 -0
  37. package/hooks/hooks_constants/code_rules_enforcer_constants.py +16 -0
  38. package/hooks/hooks_constants/dead_dataclass_field_constants.py +25 -0
  39. package/hooks/hooks_constants/destructive_command_segment_constants.py +178 -0
  40. package/hooks/hooks_constants/duplicate_function_body_constants.py +17 -0
  41. package/hooks/hooks_constants/hook_prose_detector_consistency_constants.py +30 -0
  42. package/hooks/hooks_constants/messages.py +4 -0
  43. package/hooks/hooks_constants/session_handoff_blocker_constants.py +10 -0
  44. package/hooks/hooks_constants/subprocess_budget_completeness_constants.py +5 -0
  45. package/hooks/hooks_constants/workflow_substitution_slot_blocker_constants.py +22 -0
  46. package/hooks/workflow/auto_formatter.py +26 -1
  47. package/hooks/workflow/test_auto_formatter.py +134 -0
  48. package/package.json +1 -1
  49. package/rules/conservative-action.md +1 -0
  50. package/rules/docstring-prose-matches-implementation.md +43 -0
  51. package/rules/hook-prose-matches-detector.md +26 -0
  52. package/rules/long-horizon-autonomy.md +43 -0
  53. package/rules/no-inline-destructive-literals.md +11 -0
  54. package/rules/workflow-substitution-slots.md +7 -0
  55. package/skills/autoconverge/SKILL.md +68 -6
  56. package/skills/autoconverge/reference/closing-report.md +44 -0
  57. package/skills/autoconverge/reference/convergence.md +7 -3
  58. package/skills/autoconverge/reference/stop-conditions.md +7 -2
  59. package/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  60. package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +105 -0
  61. package/skills/autoconverge/workflow/converge.contract.test.mjs +30 -1
  62. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +265 -0
  63. package/skills/autoconverge/workflow/converge.mjs +106 -38
  64. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -0
  65. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -0
  66. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -0
  67. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -0
  68. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -0
  69. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -0
  70. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -0
  71. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -0
  72. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +259 -0
  73. package/skills/autoconverge/workflow/render_report.py +903 -0
  74. package/skills/autoconverge/workflow/test_render_report.py +484 -0
  75. package/skills/pr-converge/scripts/check_convergence.py +195 -64
  76. package/skills/pr-converge/scripts/test_check_convergence.py +173 -2
  77. package/skills/update/SKILL.md +37 -5
@@ -54,6 +54,101 @@ def _mask_string_literals_preserving_length(source_line: str) -> str:
54
54
  return string_literal_pattern.sub(_replace_string_literal, source_line)
55
55
 
56
56
 
57
+ def _carries_top_level_slice_colon(bracket_body: str) -> bool:
58
+ """Return True when ``bracket_body`` holds a slice colon at its own level.
59
+
60
+ The body is the text between one ``[...]`` pair. A slice colon is a ``:``
61
+ that sits at the body's top level — not nested inside any ``(``, ``[``,
62
+ or ``{`` opened within the body — and is not the ``:=`` walrus operator.
63
+ A slice writes ``[:N]`` or ``[1:N]``; a plain subscript (``[K]``), a
64
+ walrus subscript (``[(n := M)]``), and a lambda subscript
65
+ (``[(lambda: V)()]``) carry no such colon.
66
+ """
67
+ nesting_depth = 0
68
+ for each_position, each_character in enumerate(bracket_body):
69
+ if each_character in "([{":
70
+ nesting_depth += 1
71
+ continue
72
+ if each_character in ")]}":
73
+ nesting_depth -= 1
74
+ continue
75
+ if each_character == ":" and nesting_depth == 0:
76
+ next_position = each_position + 1
77
+ character_after_colon = bracket_body[next_position : next_position + 1]
78
+ follows_walrus = character_after_colon == "="
79
+ if not follows_walrus:
80
+ return True
81
+ return False
82
+
83
+
84
+ def _innermost_bracket_body_around(masked_line: str, occurrence_position: int) -> str | None:
85
+ """Return the body of the innermost ``[...]`` pair enclosing a position.
86
+
87
+ Walks the line tracking open-bracket positions on a stack. The bracket
88
+ pair directly enclosing ``occurrence_position`` is the one whose ``[``
89
+ is on top of the stack when that position is reached; its body runs from
90
+ just after that ``[`` to its matching ``]``. Returns ``None`` when the
91
+ position lies outside every ``[...]`` pair.
92
+ """
93
+ open_bracket_positions: list[int] = []
94
+ enclosing_open_position = -1
95
+ for each_position, each_character in enumerate(masked_line):
96
+ if each_position == occurrence_position:
97
+ enclosing_open_position = open_bracket_positions[-1] if open_bracket_positions else -1
98
+ if each_character == "[":
99
+ open_bracket_positions.append(each_position)
100
+ continue
101
+ if each_character == "]" and open_bracket_positions:
102
+ open_bracket_positions.pop()
103
+
104
+ if enclosing_open_position == -1:
105
+ return None
106
+
107
+ closing_position = _matching_close_bracket_position(masked_line, enclosing_open_position)
108
+ if closing_position == -1:
109
+ return None
110
+ return masked_line[enclosing_open_position + 1 : closing_position]
111
+
112
+
113
+ def _matching_close_bracket_position(masked_line: str, open_position: int) -> int:
114
+ """Return the index of the ``]`` that closes the ``[`` at ``open_position``.
115
+
116
+ Returns ``-1`` when the opening bracket has no matching close on the line.
117
+ """
118
+ depth = 0
119
+ for each_position in range(open_position, len(masked_line)):
120
+ each_character = masked_line[each_position]
121
+ if each_character == "[":
122
+ depth += 1
123
+ continue
124
+ if each_character == "]":
125
+ depth -= 1
126
+ if depth == 0:
127
+ return each_position
128
+ return -1
129
+
130
+
131
+ def _is_magic_number_inside_slice_bound(masked_line: str, number_text: str) -> bool:
132
+ """Return True when ``number_text`` appears as a slice bound on the line.
133
+
134
+ For each standalone-token occurrence of ``number_text``, the innermost
135
+ ``[...]`` pair directly enclosing it is located. The number is a slice
136
+ bound only when that innermost pair carries a top-level slice colon
137
+ (``sha[:N]``, ``ts[1:N]``). A plain subscript index (``items[K]``), the
138
+ inner index of a nested subscript (``a[b[I]:N]`` — the inner index sits
139
+ inside the plain inner pair), a walrus subscript (``arr[(n := M)]``), and
140
+ a lambda subscript (``seq[(lambda: V)()]``) are not slice bounds.
141
+ """
142
+ token_boundary_pattern = r"(?<![.\w])" + re.escape(number_text) + r"(?![.\w])"
143
+ for each_match in re.finditer(token_boundary_pattern, masked_line):
144
+ bracket_body = _innermost_bracket_body_around(masked_line, each_match.start())
145
+ if bracket_body is None:
146
+ continue
147
+ if _carries_top_level_slice_colon(bracket_body):
148
+ return True
149
+ return False
150
+
151
+
57
152
  def check_magic_values(content: str, file_path: str) -> list[str]:
58
153
  """Check for magic values in function bodies."""
59
154
  if is_config_file(file_path) or is_test_file(file_path):
@@ -93,6 +188,9 @@ def check_magic_values(content: str, file_path: str) -> list[str]:
93
188
  if each_number not in allowed_numbers:
94
189
  if "range(" in stripped_without_string_literals or "enumerate(" in stripped_without_string_literals:
95
190
  continue
191
+ if _is_magic_number_inside_slice_bound(stripped_without_string_literals, each_number):
192
+ issues.append(f"Line {each_line_number}: Magic value {each_number} - extract to named constant")
193
+ break
96
194
  if "[" in stripped_without_string_literals and "]" in stripped_without_string_literals:
97
195
  continue
98
196
  issues.append(f"Line {each_line_number}: Magic value {each_number} - extract to named constant")
@@ -115,6 +115,47 @@ def _collect_annotated_arguments(function_node: ast.FunctionDef | ast.AsyncFunct
115
115
  return all_annotated_arguments
116
116
 
117
117
 
118
+ def _collect_fixture_injection_arguments(
119
+ function_node: ast.FunctionDef | ast.AsyncFunctionDef,
120
+ ) -> list[ast.arg]:
121
+ """Return only the named parameters pytest fills by fixture injection.
122
+
123
+ Pytest passes fixtures by keyword (``testfunction(**testargs)``), so a
124
+ parameter can receive a fixture only when both conditions hold: it is
125
+ reachable by keyword, and pytest is responsible for supplying its value.
126
+ Positional-only parameters are NOT injection slots — a keyword-passed
127
+ fixture can never bind to one, and ``def test_x(tmp_path, /)`` raises a
128
+ missing-argument ``TypeError`` under pytest. A ``*args`` star-argument or
129
+ ``**kwargs`` double-star-argument never names a single fixture either.
130
+ A parameter carrying a default is NOT injected — pytest leaves its default
131
+ in place rather than supplying the fixture. So this collector keeps only the
132
+ positional-or-keyword and keyword-only parameters that have no default, and
133
+ omits ``args.posonlyargs``, ``args.vararg``, and ``args.kwarg``.
134
+
135
+ Args:
136
+ function_node: The function definition AST node to inspect.
137
+
138
+ Returns:
139
+ The undefaulted positional-or-keyword and keyword-only argument nodes,
140
+ in declaration order.
141
+ """
142
+ arguments = function_node.args
143
+ defaulted_positional_count = len(arguments.defaults)
144
+ undefaulted_positional_arguments = (
145
+ arguments.args[:-defaulted_positional_count]
146
+ if defaulted_positional_count
147
+ else arguments.args
148
+ )
149
+ undefaulted_keyword_only_arguments = [
150
+ each_keyword_argument
151
+ for each_keyword_argument, each_default in zip(
152
+ arguments.kwonlyargs, arguments.kw_defaults
153
+ )
154
+ if each_default is None
155
+ ]
156
+ return [*undefaulted_positional_arguments, *undefaulted_keyword_only_arguments]
157
+
158
+
118
159
  def _collect_target_names(target: ast.expr) -> list[ast.Name]:
119
160
  """Return every ast.Name reachable through tuple/list/starred unpacking targets."""
120
161
  if isinstance(target, ast.Name):