claude-dev-env 2.19.0 → 2.20.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 (48) hide show
  1. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +31 -1
  2. package/.agents/skills/e-code-review/SKILL.md +12 -1
  3. package/.agents/skills/e-code-review/reference/fix.md +5 -1
  4. package/.agents/skills/e-code-review/reference/loop.md +4 -0
  5. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +66 -0
  6. package/.agents/skills/e-code-review/reference/preflight-proposal.md +40 -0
  7. package/.agents/skills/e-code-review/reference/runner-selection.md +1 -0
  8. package/.agents/skills/pr-cleanup/SKILL.md +109 -11
  9. package/_shared/pr-loop/scripts/code_rules_gate.py +29 -6
  10. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +15 -3
  11. package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +4 -0
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +47 -0
  13. package/docs/CODE_RULES.md +2 -0
  14. package/hooks/advisory/conftest.py +10 -0
  15. package/hooks/advisory/refactor_guard.py +250 -144
  16. package/hooks/advisory/refactor_guard_test_support.py +46 -0
  17. package/hooks/advisory/test_refactor_guard_advisory.py +171 -0
  18. package/hooks/advisory/test_refactor_guard_eligibility.py +166 -0
  19. package/hooks/blocking/block_main_commit.py +66 -33
  20. package/hooks/blocking/code_rules_blast_radius.py +194 -0
  21. package/hooks/blocking/code_rules_enforcer.py +12 -0
  22. package/hooks/blocking/test_block_main_commit.py +145 -0
  23. package/hooks/blocking/test_code_rules_blast_radius.py +161 -0
  24. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +1 -0
  25. package/hooks/blocking/test_destructive_command_blocker.py +154 -138
  26. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +52 -9
  27. package/hooks/blocking/test_destructive_command_blocker_patterns.py +133 -0
  28. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +71 -5
  29. package/hooks/git-hooks/AGENTS.md +1 -1
  30. package/hooks/git-hooks/git_hooks_constants/__init__.py +1 -0
  31. package/hooks/git-hooks/post_commit.py +160 -51
  32. package/hooks/git-hooks/pre_commit.py +3 -3
  33. package/hooks/git-hooks/test_post_commit.py +203 -0
  34. package/hooks/git-hooks/test_pre_commit.py +2 -2
  35. package/hooks/hooks_constants/blast_radius_constants.py +14 -0
  36. package/hooks/hooks_constants/refactor_guard_constants.py +75 -0
  37. package/hooks/hooks_constants/test_refactor_guard_constants.py +21 -0
  38. package/hooks/observability/test_instructions_loaded_logger.py +54 -0
  39. package/hooks/session/test_plugin_data_dir_cleanup.py +70 -0
  40. package/hooks/session/test_session_edit_tracker_cleanup.py +16 -3
  41. package/hooks/validation/mypy_validator.py +213 -80
  42. package/hooks/validation/test_mypy_validator.py +288 -13
  43. package/hooks/workflow/auto_formatter.py +225 -93
  44. package/hooks/workflow/investigation_tracker_reset.py +2 -0
  45. package/hooks/workflow/test_auto_formatter.py +261 -12
  46. package/hooks/workflow/test_investigation_tracker_reset.py +90 -0
  47. package/package.json +1 -1
  48. package/rules/failure-blast-radius.md +126 -0
@@ -0,0 +1,161 @@
1
+ """Tests for the blast-radius declaration check."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ _blocking_directory = str(Path(__file__).resolve().parent)
9
+ if _blocking_directory not in sys.path:
10
+ sys.path.insert(0, _blocking_directory)
11
+
12
+ from code_rules_blast_radius import check_blast_radius_declared # noqa: E402
13
+
14
+ PRODUCTION_PATH = "pipeline/asset_run.py"
15
+
16
+
17
+ def test_should_report_a_loop_raise_with_pending_blast_radius_declaration() -> None:
18
+ """A loop-body raise gets an advisory requesting its blast-radius declaration."""
19
+ content = (
20
+ "def run(all_members):\n"
21
+ " for each_member in all_members:\n"
22
+ " if each_member.requires_preparation:\n"
23
+ " raise AssetError('member requires preparation')\n"
24
+ )
25
+
26
+ all_issues = check_blast_radius_declared(content, PRODUCTION_PATH)
27
+
28
+ assert len(all_issues) == 1
29
+ assert "Line 4" in all_issues[0]
30
+
31
+
32
+ def test_should_accept_a_run_fatal_raise_inside_a_loop() -> None:
33
+ """A RunFatal type declares that the whole run ends."""
34
+ content = (
35
+ "def run(all_members):\n"
36
+ " for each_member in all_members:\n"
37
+ " if each_member.digest_differs:\n"
38
+ " raise AssetRunFatal('source bytes changed')\n"
39
+ )
40
+
41
+ assert check_blast_radius_declared(content, PRODUCTION_PATH) == []
42
+
43
+
44
+ def test_should_accept_an_item_blocked_raise_inside_a_loop() -> None:
45
+ """An ItemBlocked type declares a member-scoped stop."""
46
+ content = (
47
+ "def run(all_members):\n"
48
+ " for each_member in all_members:\n"
49
+ " if each_member.requires_resize:\n"
50
+ " raise AssetItemBlocked('member requires resizing')\n"
51
+ )
52
+
53
+ assert check_blast_radius_declared(content, PRODUCTION_PATH) == []
54
+
55
+
56
+ def test_should_accept_a_raise_wrapped_by_a_blast_radius_boundary() -> None:
57
+ """A per-member boundary catches a declared type and parks the failure."""
58
+ content = (
59
+ "def run(all_members):\n"
60
+ " for each_member in all_members:\n"
61
+ " try:\n"
62
+ " if each_member.requires_resize:\n"
63
+ " raise AssetItemBlocked('member requires resizing')\n"
64
+ " except AssetItemBlocked as failure:\n"
65
+ " park(each_member, failure)\n"
66
+ )
67
+
68
+ assert check_blast_radius_declared(content, PRODUCTION_PATH) == []
69
+
70
+
71
+ def test_should_report_runtime_error_with_a_different_declared_handler() -> None:
72
+ """A different declared handler leaves a runtime crash requiring a name."""
73
+ content = (
74
+ "def run(all_members):\n"
75
+ " for each_member in all_members:\n"
76
+ " try:\n"
77
+ " raise RuntimeError('code defect')\n"
78
+ " except AssetItemBlocked:\n"
79
+ " park(each_member)\n"
80
+ )
81
+
82
+ assert len(check_blast_radius_declared(content, PRODUCTION_PATH)) == 1
83
+
84
+
85
+ @pytest.mark.parametrize("raised_type", ["RuntimeError", "TypeError", "AttributeError", "ValueError"])
86
+ def test_should_require_corresponding_handler_for_each_explicit_raise(
87
+ raised_type: str,
88
+ ) -> None:
89
+ """Each explicit raise needs a handler naming that same type."""
90
+ content = (
91
+ "def run(all_members):\n"
92
+ " for each_member in all_members:\n"
93
+ " try:\n"
94
+ f" raise {raised_type}('code defect')\n"
95
+ " except AssetItemBlocked:\n"
96
+ " park(each_member)\n"
97
+ )
98
+
99
+ assert len(check_blast_radius_declared(content, PRODUCTION_PATH)) == 1
100
+
101
+
102
+ def test_should_accept_a_run_level_raise_during_manifest_preparation() -> None:
103
+ """A run-level raise belongs to manifest preparation."""
104
+ content = (
105
+ "def prepare(manifest):\n"
106
+ " if manifest.requires_preparation:\n"
107
+ " raise AssetError('manifest requires preparation')\n"
108
+ )
109
+
110
+ assert check_blast_radius_declared(content, PRODUCTION_PATH) == []
111
+
112
+
113
+ def test_should_accept_a_bare_reraise_inside_a_loop() -> None:
114
+ """A bare re-raise propagates an error carrying a declared radius."""
115
+ content = (
116
+ "def run(all_members):\n"
117
+ " for each_member in all_members:\n"
118
+ " try:\n"
119
+ " process(each_member)\n"
120
+ " except AssetItemBlocked:\n"
121
+ " raise\n"
122
+ )
123
+
124
+ assert check_blast_radius_declared(content, PRODUCTION_PATH) == []
125
+
126
+
127
+ def test_should_accept_test_files() -> None:
128
+ """Test modules raise freely during scenario coverage."""
129
+ content = (
130
+ "def test_records_member_failure(all_members):\n"
131
+ " for each_member in all_members:\n"
132
+ " raise AssetError('member failure')\n"
133
+ )
134
+
135
+ assert check_blast_radius_declared(content, "pipeline/test_asset_run.py") == []
136
+
137
+
138
+ def test_should_report_each_loop_raise_with_pending_blast_radius_declaration() -> None:
139
+ """Each loop-body raise gets an advisory requesting its blast-radius declaration."""
140
+ content = (
141
+ "def run(all_members):\n"
142
+ " for each_member in all_members:\n"
143
+ " if each_member.requires_source:\n"
144
+ " raise AssetError('member requires a source')\n"
145
+ " if each_member.requires_resize:\n"
146
+ " raise AssetError('member requires resizing')\n"
147
+ )
148
+
149
+ assert len(check_blast_radius_declared(content, PRODUCTION_PATH)) == 2
150
+
151
+
152
+ def test_should_ignore_raise_in_nested_helper_body() -> None:
153
+ """A nested helper body is checked at its own call boundary."""
154
+ content = (
155
+ "def run(all_members):\n"
156
+ " for each_member in all_members:\n"
157
+ " def validate_member():\n"
158
+ " raise AssetError('member validation failed')\n"
159
+ )
160
+
161
+ assert check_blast_radius_declared(content, PRODUCTION_PATH) == []
@@ -38,6 +38,7 @@ class NarrowEditFixture:
38
38
 
39
39
  ALL_SCOPE_AWARE_RULE_NAMES = frozenset(
40
40
  {
41
+ "check_blast_radius_declared",
41
42
  "check_import_block_sorted",
42
43
  "check_magic_values",
43
44
  "check_duplicate_function_body_across_files",