claude-dev-env 2.28.0 → 2.28.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.
Files changed (106) hide show
  1. package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
  2. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +16 -29
  3. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +2 -2
  4. package/docs/CODE_RULES.md +3 -3
  5. package/docs/agent-spawn-protocol.md +1 -1
  6. package/docs/references/prose-style-enforcement.md +5 -9
  7. package/hooks/blocking/AGENTS.md +3 -13
  8. package/hooks/blocking/code_rules_docstrings.py +10 -2138
  9. package/hooks/blocking/code_rules_enforcer.py +0 -121
  10. package/hooks/blocking/code_rules_imports_logging.py +1 -236
  11. package/hooks/blocking/code_rules_shared.py +23 -0
  12. package/hooks/blocking/code_rules_test_layout.py +8 -8
  13. package/hooks/blocking/config/prose_style_enforcement_constants.py +4 -2
  14. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +5 -1
  15. package/hooks/blocking/precommit_code_rules_gate.py +8 -43
  16. package/hooks/blocking/state_description_blocker.py +1 -7
  17. package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +0 -1
  18. package/hooks/blocking/test_code_rules_enforcer_dispatch_wiring.py +0 -8
  19. package/hooks/blocking/test_code_rules_enforcer_module_docstring_roster.py +11 -112
  20. package/hooks/blocking/test_code_rules_enforcer_narrow_edit.py +0 -1
  21. package/hooks/blocking/test_code_rules_enforcer_split_entry_1.py +1 -18
  22. package/hooks/blocking/test_code_rules_shared.py +12 -0
  23. package/hooks/blocking/test_precommit_code_rules_gate.py +32 -179
  24. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -1
  25. package/hooks/blocking/test_state_description_blocker.py +6 -4
  26. package/hooks/blocking/test_stop_dispatcher.py +5 -29
  27. package/hooks/hooks_constants/AGENTS.md +1 -6
  28. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -1
  29. package/hooks/hooks_constants/code_rules_enforcer_constants.py +3 -0
  30. package/hooks/hooks_constants/messages.py +0 -2
  31. package/hooks/hooks_constants/precommit_code_rules_gate_constants.py +3 -17
  32. package/hooks/hooks_constants/stop_dispatcher_constants.py +0 -2
  33. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -1
  34. package/hooks/hooks_constants/test_code_rules_enforcer_constants.py +7 -0
  35. package/hooks/hooks_constants/test_messages.py +5 -3
  36. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +0 -2
  37. package/package.json +1 -1
  38. package/rules/AGENTS.md +3 -6
  39. package/rules/claims-as-quotes.md +0 -10
  40. package/rules/code-standards.md +6 -6
  41. package/rules/explore-thoroughly.md +0 -1
  42. package/rules/failure-blast-radius.md +0 -8
  43. package/rules/falsify-before-green.md +0 -8
  44. package/rules/file-global-constants.md +2 -2
  45. package/rules/filesystem-search.md +1 -1
  46. package/rules/git-workflow.md +1 -9
  47. package/rules/hedging-claims.md +2 -6
  48. package/rules/long-horizon-autonomy.md +1 -1
  49. package/rules/measurement-denominators.md +0 -9
  50. package/rules/research-mode.md +0 -6
  51. package/rules/verify-before-asking.md +0 -5
  52. package/rules/verify-runtime-state.md +0 -5
  53. package/scripts/codex_compat_materializer.py +0 -12
  54. package/scripts/sync_to_cursor/rules.py +0 -10
  55. package/scripts/tests/test_engine.py +0 -1
  56. package/scripts/tests/test_rules.py +0 -1
  57. package/scripts/tests/test_sync_to_cursor.py +0 -1
  58. package/system-prompts/software-engineer.xml +3 -3
  59. package/hooks/blocking/code_rules_dead_argparse_argument.py +0 -554
  60. package/hooks/blocking/code_rules_dead_config_field.py +0 -568
  61. package/hooks/blocking/code_rules_dead_dataclass_field.py +0 -348
  62. package/hooks/blocking/code_rules_dead_module_constant.py +0 -757
  63. package/hooks/blocking/code_rules_dead_split_branch.py +0 -225
  64. package/hooks/blocking/code_rules_mock_completeness.py +0 -295
  65. package/hooks/blocking/code_rules_scope_binding.py +0 -151
  66. package/hooks/blocking/code_rules_unused_imports.py +0 -197
  67. package/hooks/blocking/hedging_language_blocker.py +0 -221
  68. package/hooks/blocking/intent_only_ending_blocker.py +0 -148
  69. package/hooks/blocking/test_code_rules_enforcer_dead_argparse_argument.py +0 -534
  70. package/hooks/blocking/test_code_rules_enforcer_dead_config_field.py +0 -846
  71. package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +0 -507
  72. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +0 -679
  73. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_alias.py +0 -133
  74. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant_read_cap.py +0 -103
  75. package/hooks/blocking/test_code_rules_enforcer_dead_split_branch.py +0 -105
  76. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +0 -425
  77. package/hooks/blocking/test_code_rules_enforcer_docstring_cardinal_family.py +0 -176
  78. package/hooks/blocking/test_code_rules_enforcer_docstring_delegation_summary.py +0 -385
  79. package/hooks/blocking/test_code_rules_enforcer_docstring_fallback_branch.py +0 -398
  80. package/hooks/blocking/test_code_rules_enforcer_docstring_field_runmode_outcome.py +0 -129
  81. package/hooks/blocking/test_code_rules_enforcer_docstring_inline_literal_claim.py +0 -93
  82. package/hooks/blocking/test_code_rules_enforcer_docstring_length_constant_superlative.py +0 -198
  83. package/hooks/blocking/test_code_rules_enforcer_docstring_mark_glyph_enumeration.py +0 -262
  84. package/hooks/blocking/test_code_rules_enforcer_docstring_no_consumer.py +0 -93
  85. package/hooks/blocking/test_code_rules_enforcer_docstring_no_network.py +0 -115
  86. package/hooks/blocking/test_code_rules_enforcer_docstring_raises_largezipfile.py +0 -226
  87. package/hooks/blocking/test_code_rules_enforcer_docstring_returns_plural_cardinality.py +0 -207
  88. package/hooks/blocking/test_code_rules_enforcer_docstring_step_dispatch.py +0 -262
  89. package/hooks/blocking/test_code_rules_enforcer_docstring_type_checking_gate.py +0 -164
  90. package/hooks/blocking/test_code_rules_enforcer_docstring_unguarded_payload.py +0 -188
  91. package/hooks/blocking/test_code_rules_enforcer_import_block_sort.py +0 -157
  92. package/hooks/blocking/test_code_rules_enforcer_split_mocks_1.py +0 -303
  93. package/hooks/blocking/test_code_rules_enforcer_split_mocks_2.py +0 -111
  94. package/hooks/blocking/test_code_rules_enforcer_unused_imports.py +0 -656
  95. package/hooks/blocking/test_hedging_language_blocker.py +0 -261
  96. package/hooks/blocking/test_intent_only_ending_blocker.py +0 -209
  97. package/hooks/hooks_constants/dead_argparse_argument_constants.py +0 -28
  98. package/hooks/hooks_constants/dead_config_field_constants.py +0 -39
  99. package/hooks/hooks_constants/dead_dataclass_field_constants.py +0 -25
  100. package/hooks/hooks_constants/dead_module_constant_constants.py +0 -30
  101. package/hooks/hooks_constants/hedging_uncertainty_constants.py +0 -42
  102. package/hooks/hooks_constants/test_dispatcher_constants_docstrings.py +0 -44
  103. package/rules/conservative-action.md +0 -17
  104. package/rules/context7.md +0 -8
  105. package/rules/few-words.md +0 -3
  106. package/rules/parallel-tools.md +0 -23
@@ -1,261 +0,0 @@
1
- """Tests for hedging_language_blocker hook response shape."""
2
-
3
- import importlib.util
4
- import io
5
- import json
6
- import os
7
- import subprocess
8
- import sys
9
- import tempfile
10
-
11
- import pytest
12
-
13
- HOOK_SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "hedging_language_blocker.py")
14
- _HOOKS_DIR = os.path.dirname(HOOK_SCRIPT_PATH)
15
- _HOOKS_ROOT = os.path.join(_HOOKS_DIR, "..")
16
- _HOOK_CONFIG_DIR = os.path.join(_HOOKS_ROOT, "hooks_constants")
17
- if _HOOKS_DIR not in sys.path:
18
- sys.path.insert(0, _HOOKS_DIR)
19
- if _HOOKS_ROOT not in sys.path:
20
- sys.path.insert(0, _HOOKS_ROOT)
21
- import hedging_language_blocker
22
- from hooks_constants.messages import USER_FACING_NOTICE
23
- from hooks_constants.text_stripping import strip_code_and_quotes
24
-
25
-
26
- def test_blocker_uses_shared_strip_code_and_quotes() -> None:
27
- assert hedging_language_blocker.strip_code_and_quotes is strip_code_and_quotes
28
-
29
-
30
- RESEARCH_MODE_SKILL_BODY_MARKER = "Three anti-hallucination constraints are ALWAYS active."
31
- HEDGING_MESSAGE = "This is likely correct."
32
- CLEAN_MESSAGE = "This is verified by the source document."
33
- EMPTY_MESSAGE = ""
34
-
35
-
36
- def run_hook_with_message(
37
- assistant_message: str, *, is_prose_style_enabled: bool = True
38
- ) -> subprocess.CompletedProcess:
39
- hook_input_payload = json.dumps({"last_assistant_message": assistant_message})
40
- environment_by_key = os.environ.copy()
41
- if is_prose_style_enabled:
42
- environment_by_key["CLAUDE_PROSE_STYLE_ENFORCEMENT"] = "1"
43
- else:
44
- environment_by_key.pop("CLAUDE_PROSE_STYLE_ENFORCEMENT", None)
45
- return subprocess.run(
46
- [sys.executable, HOOK_SCRIPT_PATH],
47
- input=hook_input_payload,
48
- capture_output=True,
49
- text=True,
50
- check=False,
51
- env=environment_by_key,
52
- )
53
-
54
-
55
- def run_hook_with_patched_search_paths(
56
- assistant_message: str,
57
- search_paths: list[str],
58
- ) -> subprocess.CompletedProcess:
59
- """Run the hook with RESEARCH_MODE_SKILL_SEARCH_PATHS overridden via a wrapper script."""
60
- wrapper_script = (
61
- "import sys, json, os\n"
62
- f"sys.path.insert(0, {repr(os.path.dirname(HOOK_SCRIPT_PATH))})\n"
63
- "import hedging_language_blocker as blocker\n"
64
- f"blocker.RESEARCH_MODE_SKILL_SEARCH_PATHS = {repr(search_paths)}\n"
65
- "blocker.main()\n"
66
- )
67
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as wrapper_file:
68
- wrapper_file.write(wrapper_script)
69
- wrapper_file_path = wrapper_file.name
70
-
71
- hook_input_payload = json.dumps({"last_assistant_message": assistant_message})
72
- environment_by_key = os.environ.copy()
73
- environment_by_key["CLAUDE_PROSE_STYLE_ENFORCEMENT"] = "1"
74
- try:
75
- completed_process = subprocess.run(
76
- [sys.executable, wrapper_file_path],
77
- input=hook_input_payload,
78
- capture_output=True,
79
- text=True,
80
- check=False,
81
- env=environment_by_key,
82
- )
83
- finally:
84
- os.unlink(wrapper_file_path)
85
- return completed_process
86
-
87
-
88
- def test_user_facing_notice_matches_config_messages_module():
89
- config_messages_path = os.path.join(_HOOK_CONFIG_DIR, "messages.py")
90
- specification = importlib.util.spec_from_file_location("messages", config_messages_path)
91
- module = importlib.util.module_from_spec(specification)
92
- specification.loader.exec_module(module)
93
-
94
- assert module.USER_FACING_NOTICE == USER_FACING_NOTICE
95
-
96
-
97
- def test_hedging_scan_is_default_off() -> None:
98
- completed_process = run_hook_with_message(
99
- HEDGING_MESSAGE, is_prose_style_enabled=False
100
- )
101
-
102
- assert completed_process.returncode == 0
103
- assert completed_process.stdout == ""
104
-
105
-
106
- def test_default_off_emits_privacy_safe_advisory_candidates(
107
- monkeypatch: pytest.MonkeyPatch,
108
- ) -> None:
109
- all_emitted: list[tuple[str, str, str]] = []
110
-
111
- def _record_advisory(
112
- matcher_id: str, surface: str, context_text: str, **_kwargs: object
113
- ) -> dict[str, object]:
114
- all_emitted.append((matcher_id, surface, context_text))
115
- return {}
116
-
117
- monkeypatch.setattr(
118
- hedging_language_blocker, "emit_advisory_candidate", _record_advisory
119
- )
120
- monkeypatch.setattr(
121
- hedging_language_blocker,
122
- "prose_style_enforcement_enabled_in_environment",
123
- lambda: False,
124
- )
125
- monkeypatch.setattr(
126
- hedging_language_blocker.sys,
127
- "stdin",
128
- io.StringIO(json.dumps({"last_assistant_message": HEDGING_MESSAGE})),
129
- )
130
- with pytest.raises(SystemExit) as exit_info:
131
- hedging_language_blocker.main()
132
- assert exit_info.value.code == 0
133
- assert all_emitted
134
- matcher_id, surface, context_text = all_emitted[0]
135
- assert matcher_id == "hedging_word"
136
- assert surface == "Stop"
137
- assert "likely" in context_text
138
-
139
-
140
- def test_hedging_message_emits_block_with_short_user_notice():
141
- completed_process = run_hook_with_message(HEDGING_MESSAGE)
142
-
143
- assert completed_process.returncode == 0
144
- parsed_response = json.loads(completed_process.stdout)
145
-
146
- assert parsed_response["decision"] == "block"
147
- assert parsed_response["systemMessage"] == USER_FACING_NOTICE
148
- assert parsed_response["suppressOutput"] is True
149
- assert "likely" in parsed_response["reason"]
150
-
151
-
152
- def test_hedging_reason_contains_not_installed_notice_when_skill_absent():
153
- completed_process = run_hook_with_patched_search_paths(
154
- HEDGING_MESSAGE,
155
- ["/nonexistent/path/one/SKILL.md", "/nonexistent/path/two/SKILL.md"],
156
- )
157
-
158
- assert completed_process.returncode == 0
159
- parsed_response = json.loads(completed_process.stdout)
160
-
161
- assert parsed_response["decision"] == "block"
162
- assert "no research-mode skill installed" in parsed_response["reason"]
163
- assert "verify with sources or prompt the user via AskUserQuestion" in parsed_response["reason"]
164
- assert "SKILL.md" not in parsed_response["reason"]
165
- assert RESEARCH_MODE_SKILL_BODY_MARKER not in parsed_response["reason"]
166
-
167
-
168
- def test_hedging_reason_contains_skill_path_when_skill_present():
169
- with tempfile.TemporaryDirectory() as skill_dir:
170
- skill_file_path = os.path.join(skill_dir, "SKILL.md")
171
- with open(skill_file_path, "w") as skill_file:
172
- skill_file.write("# Research Mode Skill\n")
173
-
174
- completed_process = run_hook_with_patched_search_paths(
175
- HEDGING_MESSAGE,
176
- ["/nonexistent/path/SKILL.md", skill_file_path],
177
- )
178
-
179
- assert completed_process.returncode == 0
180
- parsed_response = json.loads(completed_process.stdout)
181
-
182
- assert parsed_response["decision"] == "block"
183
- assert "SKILL.md" in parsed_response["reason"]
184
- assert "no research-mode skill installed" not in parsed_response["reason"]
185
- assert RESEARCH_MODE_SKILL_BODY_MARKER not in parsed_response["reason"]
186
-
187
-
188
- def test_clean_message_passes_through_with_no_output():
189
- completed_process = run_hook_with_message(CLEAN_MESSAGE)
190
-
191
- assert completed_process.returncode == 0
192
- assert completed_process.stdout == ""
193
-
194
-
195
- def test_empty_message_passes_through_with_no_output():
196
- completed_process = run_hook_with_message(EMPTY_MESSAGE)
197
-
198
- assert completed_process.returncode == 0
199
- assert completed_process.stdout == ""
200
-
201
-
202
- def test_explicit_unverified_label_in_same_sentence_passes() -> None:
203
- completed_process = run_hook_with_message(
204
- "This claim is unverified; the deploy is probably blocked."
205
- )
206
-
207
- assert completed_process.returncode == 0
208
- assert completed_process.stdout == ""
209
-
210
-
211
- def test_i_dont_know_label_in_same_sentence_passes() -> None:
212
- completed_process = run_hook_with_message(
213
- "I don't know whether the port is probably open."
214
- )
215
-
216
- assert completed_process.returncode == 0
217
- assert completed_process.stdout == ""
218
-
219
-
220
- def test_supported_probability_without_hedge_word_passes() -> None:
221
- completed_process = run_hook_with_message(
222
- "The suite reports 0.92 precision on the labeled fixture set in "
223
- "test_prose_matcher_advisory.py."
224
- )
225
-
226
- assert completed_process.returncode == 0
227
- assert completed_process.stdout == ""
228
-
229
-
230
- def test_label_in_one_sentence_does_not_exempt_bare_hedge_in_another() -> None:
231
- completed_process = run_hook_with_message(
232
- "This claim is unverified. The deploy is probably blocked."
233
- )
234
-
235
- assert completed_process.returncode == 0
236
- parsed_response = json.loads(completed_process.stdout)
237
- assert parsed_response["decision"] == "block"
238
- assert "probably" in parsed_response["reason"]
239
- assert "explicit uncertainty label" in parsed_response["reason"]
240
-
241
-
242
- def test_bare_probably_still_blocks_with_positive_corrective() -> None:
243
- completed_process = run_hook_with_message("The deploy is probably blocked.")
244
-
245
- assert completed_process.returncode == 0
246
- parsed_response = json.loads(completed_process.stdout)
247
- assert parsed_response["decision"] == "block"
248
- assert "probably" in parsed_response["reason"]
249
- assert "label that claim unverified" in parsed_response["reason"]
250
- assert "AskUserQuestion" in parsed_response["reason"]
251
-
252
-
253
- def test_find_blocking_hedging_terms_is_sentence_scoped() -> None:
254
- bare = hedging_language_blocker.find_blocking_hedging_terms(
255
- "This claim is unverified. The deploy is probably blocked."
256
- )
257
- labeled = hedging_language_blocker.find_blocking_hedging_terms(
258
- "This claim is unverified; the deploy is probably blocked."
259
- )
260
- assert bare == ["probably"]
261
- assert labeled == []
@@ -1,209 +0,0 @@
1
- """Tests for intent_only_ending_blocker hook response shape."""
2
-
3
- import importlib.util
4
- import json
5
- import os
6
- import subprocess
7
- import sys
8
-
9
- HOOK_SCRIPT_PATH = os.path.join(os.path.dirname(__file__), "intent_only_ending_blocker.py")
10
- _HOOKS_DIR = os.path.dirname(HOOK_SCRIPT_PATH)
11
- _HOOKS_ROOT = os.path.join(_HOOKS_DIR, "..")
12
- _HOOK_CONFIG_DIR = os.path.join(_HOOKS_ROOT, "hooks_constants")
13
- if _HOOKS_DIR not in sys.path:
14
- sys.path.insert(0, _HOOKS_DIR)
15
- if _HOOKS_ROOT not in sys.path:
16
- sys.path.insert(0, _HOOKS_ROOT)
17
- import intent_only_ending_blocker
18
- from hooks_constants.messages import USER_FACING_INTENT_ENDING_NOTICE
19
- from hooks_constants.text_stripping import strip_code_and_quotes
20
-
21
- def test_blocker_uses_shared_strip_code_and_quotes() -> None:
22
- assert intent_only_ending_blocker.strip_code_and_quotes is strip_code_and_quotes
23
-
24
-
25
- INTENT_ENDING_MESSAGE = "I'll now run the test suite and fix any failures that come up."
26
- NEXT_STEPS_MESSAGE = "Next steps:"
27
- COMPLETED_WORK_MESSAGE = "Done - all tests pass. The fix is in place."
28
- BENIGN_PAST_TENSE_MESSAGE = "I implemented the parser and verified it against the fixtures."
29
- CLEAN_MESSAGE = "The function returns the parsed payload to its caller."
30
- COMMITMENT_PHRASING_MESSAGE = "I'll commit to keeping the API stable across the next release."
31
- NAMING_COMMITMENT_MESSAGE = "Let me commit to a clear naming convention for these helpers."
32
- GIT_COMMIT_INTENT_MESSAGE = "I'll commit the changes once the tests pass."
33
- DEFERRED_TO_USER_CI_RUNS_MESSAGE = (
34
- "Let me know if this looks right. The CI will run automatically on push."
35
- )
36
- DEFERRED_TO_USER_EITHER_WAY_MESSAGE = (
37
- "Let me know which option you want. I can check either way."
38
- )
39
- USER_DIRECTED_NEXT_STEPS_MESSAGE = (
40
- "All done. The PR is merged.\n\nNext steps: you should deploy when ready."
41
- )
42
- EMPTY_MESSAGE = ""
43
-
44
-
45
- def run_hook_with_message(
46
- assistant_message: str, *, is_prose_style_enabled: bool = True
47
- ) -> subprocess.CompletedProcess:
48
- hook_input_payload = json.dumps({"last_assistant_message": assistant_message})
49
- environment_by_key = os.environ.copy()
50
- if is_prose_style_enabled:
51
- environment_by_key["CLAUDE_PROSE_STYLE_ENFORCEMENT"] = "1"
52
- else:
53
- environment_by_key.pop("CLAUDE_PROSE_STYLE_ENFORCEMENT", None)
54
- return subprocess.run(
55
- [sys.executable, HOOK_SCRIPT_PATH],
56
- input=hook_input_payload,
57
- capture_output=True,
58
- text=True,
59
- check=False,
60
- env=environment_by_key,
61
- )
62
-
63
-
64
- def run_hook_with_payload(
65
- hook_input_payload: dict, *, is_prose_style_enabled: bool = True
66
- ) -> subprocess.CompletedProcess:
67
- environment_by_key = os.environ.copy()
68
- if is_prose_style_enabled:
69
- environment_by_key["CLAUDE_PROSE_STYLE_ENFORCEMENT"] = "1"
70
- else:
71
- environment_by_key.pop("CLAUDE_PROSE_STYLE_ENFORCEMENT", None)
72
- return subprocess.run(
73
- [sys.executable, HOOK_SCRIPT_PATH],
74
- input=json.dumps(hook_input_payload),
75
- capture_output=True,
76
- text=True,
77
- check=False,
78
- env=environment_by_key,
79
- )
80
-
81
-
82
- def test_user_facing_notice_matches_config_messages_module():
83
- config_messages_path = os.path.join(_HOOK_CONFIG_DIR, "messages.py")
84
- specification = importlib.util.spec_from_file_location("messages", config_messages_path)
85
- module = importlib.util.module_from_spec(specification)
86
- specification.loader.exec_module(module)
87
-
88
- assert module.USER_FACING_INTENT_ENDING_NOTICE == USER_FACING_INTENT_ENDING_NOTICE
89
- assert (
90
- intent_only_ending_blocker.USER_FACING_INTENT_ENDING_NOTICE
91
- == module.USER_FACING_INTENT_ENDING_NOTICE
92
- )
93
-
94
-
95
- def test_intent_scan_is_default_off() -> None:
96
- completed_process = run_hook_with_message(
97
- INTENT_ENDING_MESSAGE, is_prose_style_enabled=False
98
- )
99
-
100
- assert completed_process.returncode == 0
101
- assert completed_process.stdout == ""
102
-
103
-
104
- def test_intent_ending_message_emits_block_with_short_user_notice():
105
- completed_process = run_hook_with_message(INTENT_ENDING_MESSAGE)
106
-
107
- assert completed_process.returncode == 0
108
- parsed_response = json.loads(completed_process.stdout)
109
-
110
- assert parsed_response["decision"] == "block"
111
- assert parsed_response["systemMessage"] == USER_FACING_INTENT_ENDING_NOTICE
112
- assert parsed_response["suppressOutput"] is True
113
- assert "long-horizon-autonomy" in parsed_response["reason"]
114
- assert "This turn ends with a promise of future work." in parsed_response["reason"]
115
- assert "Complete that work with tool calls during this turn." in parsed_response[
116
- "reason"
117
- ]
118
-
119
-
120
- def test_next_steps_lead_in_emits_block():
121
- completed_process = run_hook_with_message(NEXT_STEPS_MESSAGE)
122
-
123
- assert completed_process.returncode == 0
124
- parsed_response = json.loads(completed_process.stdout)
125
-
126
- assert parsed_response["decision"] == "block"
127
- assert parsed_response["systemMessage"] == USER_FACING_INTENT_ENDING_NOTICE
128
-
129
-
130
- def test_completed_work_summary_passes_through_with_no_output():
131
- completed_process = run_hook_with_message(COMPLETED_WORK_MESSAGE)
132
-
133
- assert completed_process.returncode == 0
134
- assert completed_process.stdout == ""
135
-
136
-
137
- def test_past_tense_summary_passes_through_with_no_output():
138
- completed_process = run_hook_with_message(BENIGN_PAST_TENSE_MESSAGE)
139
-
140
- assert completed_process.returncode == 0
141
- assert completed_process.stdout == ""
142
-
143
-
144
- def test_commitment_phrasing_passes_through_with_no_output():
145
- completed_process = run_hook_with_message(COMMITMENT_PHRASING_MESSAGE)
146
-
147
- assert completed_process.returncode == 0
148
- assert completed_process.stdout == ""
149
-
150
-
151
- def test_naming_commitment_passes_through_with_no_output():
152
- completed_process = run_hook_with_message(NAMING_COMMITMENT_MESSAGE)
153
-
154
- assert completed_process.returncode == 0
155
- assert completed_process.stdout == ""
156
-
157
-
158
- def test_git_commit_intent_emits_block():
159
- completed_process = run_hook_with_message(GIT_COMMIT_INTENT_MESSAGE)
160
-
161
- assert completed_process.returncode == 0
162
- parsed_response = json.loads(completed_process.stdout)
163
-
164
- assert parsed_response["decision"] == "block"
165
- assert parsed_response["systemMessage"] == USER_FACING_INTENT_ENDING_NOTICE
166
-
167
-
168
- def test_benign_opener_with_unrelated_work_verb_passes_through_with_no_output():
169
- completed_process = run_hook_with_message(DEFERRED_TO_USER_CI_RUNS_MESSAGE)
170
-
171
- assert completed_process.returncode == 0
172
- assert completed_process.stdout == ""
173
-
174
-
175
- def test_deferral_to_user_with_later_verb_passes_through_with_no_output():
176
- completed_process = run_hook_with_message(DEFERRED_TO_USER_EITHER_WAY_MESSAGE)
177
-
178
- assert completed_process.returncode == 0
179
- assert completed_process.stdout == ""
180
-
181
-
182
- def test_user_directed_next_steps_passes_through_with_no_output():
183
- completed_process = run_hook_with_message(USER_DIRECTED_NEXT_STEPS_MESSAGE)
184
-
185
- assert completed_process.returncode == 0
186
- assert completed_process.stdout == ""
187
-
188
-
189
- def test_clean_message_passes_through_with_no_output():
190
- completed_process = run_hook_with_message(CLEAN_MESSAGE)
191
-
192
- assert completed_process.returncode == 0
193
- assert completed_process.stdout == ""
194
-
195
-
196
- def test_empty_message_passes_through_with_no_output():
197
- completed_process = run_hook_with_message(EMPTY_MESSAGE)
198
-
199
- assert completed_process.returncode == 0
200
- assert completed_process.stdout == ""
201
-
202
-
203
- def test_stop_hook_active_short_circuits_with_no_output():
204
- completed_process = run_hook_with_payload(
205
- {"last_assistant_message": INTENT_ENDING_MESSAGE, "stop_hook_active": True}
206
- )
207
-
208
- assert completed_process.returncode == 0
209
- assert completed_process.stdout == ""
@@ -1,28 +0,0 @@
1
- """Constants for the dead argparse-argument detector in ``code_rules_enforcer``.
2
-
3
- Lives under the hooks-tree ``hooks_constants`` package so module-level
4
- UPPER_SNAKE constants satisfy the CODE_RULES "constants live in config"
5
- requirement and share a home with the other hook-tree configuration.
6
- """
7
-
8
- ADD_ARGUMENT_METHOD_NAME: str = "add_argument"
9
- ALL_PARSE_METHOD_NAMES: frozenset[str] = frozenset({"parse_args", "parse_known_args"})
10
- DEST_KEYWORD_NAME: str = "dest"
11
- NAMESPACE_KEYWORD_NAME: str = "namespace"
12
- ACTION_KEYWORD_NAME: str = "action"
13
- ALL_SUPPRESSED_ACTION_NAMES: frozenset[str] = frozenset({"help", "version"})
14
- GETATTR_FUNCTION_NAME: str = "getattr"
15
- GETATTR_NAME_ARGUMENT_MINIMUM: int = 2
16
- ATTRGETTER_FUNCTION_NAME: str = "attrgetter"
17
- NAMESPACE_DICT_ATTRIBUTE_NAME: str = "__dict__"
18
- OPTION_PREFIX: str = "-"
19
- LONG_OPTION_PREFIX: str = "--"
20
- DEST_WORD_SEPARATOR: str = "-"
21
- DEST_WORD_JOINER: str = "_"
22
- EXPORTED_NAMES_ATTRIBUTE: str = "__all__"
23
- MAX_DEAD_ARGPARSE_ARGUMENT_ISSUES: int = 25
24
- DEAD_ARGPARSE_ARGUMENT_GUIDANCE: str = (
25
- "optional CLI flag whose parsed value is never read in this file - remove the"
26
- " add_argument call (argparse silently accepts an unused flag), or read the"
27
- " parsed value where it is needed (CODE_RULES §9.8)"
28
- )
@@ -1,39 +0,0 @@
1
- """Constants for the dead config-dataclass field detector in ``code_rules_enforcer``.
2
-
3
- Lives under the hooks-tree ``hooks_constants`` package so module-level
4
- UPPER_SNAKE constants satisfy the CODE_RULES "constants live in config"
5
- requirement and share a home with the other hook-tree configuration.
6
- """
7
-
8
- from hooks_constants.dead_dataclass_field_constants import (
9
- ALL_REFLECTIVE_FIELD_CONSUMER_NAMES,
10
- WHOLE_INSTANCE_DICT_ATTRIBUTE_NAME,
11
- )
12
- from hooks_constants.dead_module_constant_constants import (
13
- CONFIG_DIRECTORY_SEGMENT,
14
- DUNDER_INIT_FILENAME,
15
- MAX_SCAN_ROOT_FILE_COUNT,
16
- PYTHON_SOURCE_SUFFIX,
17
- )
18
-
19
- ALL_CONFIG_CLASS_NAME_SUFFIXES: tuple[str, ...] = ("Config", "Selectors")
20
- DATACLASSES_MODULE_NAME: str = "dataclasses"
21
- MAX_DEAD_CONFIG_FIELD_ISSUES: int = 25
22
- DEAD_CONFIG_FIELD_GUIDANCE: str = (
23
- "config or selectors dataclass field is defined but read by no production"
24
- " module in the enclosing package tree - remove the dead field, or read it"
25
- " where the value is needed (CODE_RULES §9.8)"
26
- )
27
-
28
- __all__ = [
29
- "ALL_CONFIG_CLASS_NAME_SUFFIXES",
30
- "ALL_REFLECTIVE_FIELD_CONSUMER_NAMES",
31
- "CONFIG_DIRECTORY_SEGMENT",
32
- "DATACLASSES_MODULE_NAME",
33
- "DEAD_CONFIG_FIELD_GUIDANCE",
34
- "DUNDER_INIT_FILENAME",
35
- "MAX_DEAD_CONFIG_FIELD_ISSUES",
36
- "MAX_SCAN_ROOT_FILE_COUNT",
37
- "PYTHON_SOURCE_SUFFIX",
38
- "WHOLE_INSTANCE_DICT_ATTRIBUTE_NAME",
39
- ]
@@ -1,25 +0,0 @@
1
- """Constants for the dead dataclass-field detector in ``code_rules_enforcer``.
2
-
3
- Lives under the hooks-tree ``hooks_constants`` package so module-level
4
- UPPER_SNAKE constants satisfy the CODE_RULES "constants live in config"
5
- requirement and share a home with the other hook-tree configuration.
6
- """
7
-
8
- ALL_DATACLASS_DECORATOR_NAMES: frozenset[str] = frozenset({"dataclass", "dataclasses"})
9
- ATTRGETTER_FUNCTION_NAME: str = "attrgetter"
10
- CLASSVAR_ANNOTATION_NAME: str = "ClassVar"
11
- GETATTR_FUNCTION_NAME: str = "getattr"
12
- GETATTR_NAME_ARGUMENT_MINIMUM: int = 2
13
- ALL_REFLECTIVE_FIELD_CONSUMER_NAMES: frozenset[str] = frozenset(
14
- {"asdict", "astuple", "fields", "replace", "vars"}
15
- )
16
- WHOLE_INSTANCE_DICT_ATTRIBUTE_NAME: str = "__dict__"
17
- ALL_WHOLE_INSTANCE_STRINGIFY_NAMES: frozenset[str] = frozenset(
18
- {"str", "repr", "format"}
19
- )
20
- MAX_DEAD_DATACLASS_FIELD_ISSUES: int = 25
21
- DEAD_DATACLASS_FIELD_GUIDANCE: str = (
22
- "field is assigned but never read in this file - remove the field and the code"
23
- " that only exists to populate it, or read it where the value is needed"
24
- " (CODE_RULES §9.8)"
25
- )
@@ -1,30 +0,0 @@
1
- """Constants for the dead module-level constant detector in ``code_rules_enforcer``.
2
-
3
- Lives under the hooks-tree ``hooks_constants`` package so module-level
4
- UPPER_SNAKE constants satisfy the CODE_RULES "constants live in config"
5
- requirement and share a home with the other hook-tree configuration.
6
- """
7
-
8
- PYTHON_SOURCE_SUFFIX: str = ".py"
9
- DUNDER_INIT_FILENAME: str = "__init__.py"
10
- CONSTANTS_MODULE_SUFFIX: str = "_constants.py"
11
- CONFIG_DIRECTORY_SEGMENT: str = "config"
12
- DUNDER_ALL_NAME: str = "__all__"
13
- GIT_DIRECTORY_NAME: str = ".git"
14
- MINIMUM_UPPER_SNAKE_LENGTH: int = 2
15
- MAX_DEAD_MODULE_CONSTANT_ISSUES: int = 25
16
- MAX_SCAN_ROOT_FILE_COUNT: int = 2000
17
- MAX_SCAN_ROOT_READ_COUNT: int = 20000
18
- DEAD_MODULE_CONSTANT_GUIDANCE: str = (
19
- "module-level constant is defined here but never imported or read by any"
20
- " module in the enclosing package tree - remove the constant, or reference it"
21
- " where its value is needed (CODE_RULES §9.8)"
22
- )
23
- DEAD_MODULE_CONSTANT_RETRY_GUIDANCE: str = (
24
- "To land a constant a consumer will read, break the write-order deadlock by"
25
- " writing the consumer that reads it first: that write completes even though a"
26
- " transient mypy attr-defined advisory flags the not-yet-defined name (a"
27
- " non-blocking post-write check), then re-issue this write, which passes once"
28
- " the consumer is on disk. A constant no module ever reads stays flagged on"
29
- " every attempt."
30
- )
@@ -1,42 +0,0 @@
1
- """Explicit uncertainty labels that co-occur with hedge words in one sentence.
2
-
3
- ::
4
-
5
- ok: This claim is unverified; the port is probably down.
6
- flag: The port is probably down. (no label in that sentence)
7
-
8
- A response-wide "unverified" label does not exempt a bare hedge in another
9
- sentence. Classification of matcher precision (OP-07B) uses the same blocking
10
- surface: only hedges that survive this filter count toward hard-block evidence.
11
- """
12
-
13
- from __future__ import annotations
14
-
15
- import re
16
-
17
- # Sentence ends at . ! ? when followed by space or end; keep the terminator
18
- # attached to the sentence so labels next to periods still match.
19
- SENTENCE_BOUNDARY_PATTERN = re.compile(r"(?<=[.!?])\s+")
20
-
21
- ALL_EXPLICIT_UNCERTAINTY_LABEL_PATTERNS = (
22
- re.compile(r"\bunverified\b", re.IGNORECASE),
23
- re.compile(r"\bi don't know\b", re.IGNORECASE),
24
- re.compile(r"\bi do not know\b", re.IGNORECASE),
25
- re.compile(r"\bno source for this claim\b", re.IGNORECASE),
26
- re.compile(r"\bwithout a (credible )?source\b", re.IGNORECASE),
27
- re.compile(r"\bnamed (as )?unverified\b", re.IGNORECASE),
28
- )
29
-
30
- HEDGING_TERM_LIST_SEPARATOR = ", "
31
-
32
- POSITIVE_CORRECTIVE_GUIDANCE = (
33
- "State the claim with evidence that backs it, or label that claim unverified "
34
- "in the same sentence as the uncertainty word. "
35
- "Gather a source, run a live probe, or ask the user through AskUserQuestion — "
36
- "then re-output the complete revised response."
37
- )
38
-
39
- BLOCK_REASON_PREFIX = (
40
- "ANTI-HALLUCINATION GUARDRAIL: Your response contains hedging language without "
41
- "an explicit uncertainty label in the same sentence: "
42
- )
@@ -1,44 +0,0 @@
1
- """Regression tests proving the dispatcher constants docstrings clear the O6 gate.
2
-
3
- The PreToolUse and PostToolUse dispatcher constants modules each carry a module
4
- docstring stating what the module centralizes. Neither docstring may assert that
5
- no literals appear inline in its companion dispatcher script — that completeness
6
- claim about a sibling file is the deterministic Category O6 drift the enforcer's
7
- check_docstring_no_inline_literal_claim blocks. These tests load the real
8
- modules' source and assert the check returns no issues for either.
9
- """
10
-
11
- from __future__ import annotations
12
-
13
- import importlib.util
14
- from pathlib import Path
15
- from types import ModuleType
16
-
17
-
18
- def _load_enforcer_module() -> ModuleType:
19
- enforcer_path = Path(__file__).resolve().parent.parent / "blocking" / "code_rules_enforcer.py"
20
- enforcer_spec = importlib.util.spec_from_file_location("code_rules_enforcer", enforcer_path)
21
- assert enforcer_spec is not None
22
- assert enforcer_spec.loader is not None
23
- enforcer_module = importlib.util.module_from_spec(enforcer_spec)
24
- enforcer_spec.loader.exec_module(enforcer_module)
25
- return enforcer_module
26
-
27
-
28
- code_rules_enforcer = _load_enforcer_module()
29
-
30
-
31
- def _check_issues_for(module_filename: str) -> list[str]:
32
- module_path = Path(__file__).resolve().parent / module_filename
33
- module_source = module_path.read_text(encoding="utf-8")
34
- return code_rules_enforcer.check_docstring_no_inline_literal_claim(
35
- module_source, str(module_path)
36
- )
37
-
38
-
39
- def test_pre_tool_use_dispatcher_constants_docstring_clears_o6_gate() -> None:
40
- assert _check_issues_for("pre_tool_use_dispatcher_constants.py") == []
41
-
42
-
43
- def test_post_tool_use_dispatcher_constants_docstring_clears_o6_gate() -> None:
44
- assert _check_issues_for("post_tool_use_dispatcher_constants.py") == []