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.
- package/CLAUDE.md +2 -2
- package/_shared/pr-loop/scripts/code_rules_gate.py +36 -3
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +6 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +12 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +265 -0
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +29 -0
- package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +1 -1
- package/bin/install.mjs +317 -54
- package/bin/install.test.mjs +478 -3
- package/docs/CODE_RULES.md +3 -3
- package/hooks/blocking/code_rules_annotations_length.py +153 -0
- package/hooks/blocking/code_rules_dead_dataclass_field.py +319 -0
- package/hooks/blocking/code_rules_duplicate_body.py +287 -0
- package/hooks/blocking/code_rules_enforcer.py +175 -21
- package/hooks/blocking/code_rules_magic_values.py +98 -0
- package/hooks/blocking/code_rules_shared.py +41 -0
- package/hooks/blocking/destructive_command_blocker.py +1027 -12
- package/hooks/blocking/hook_prose_detector_consistency.py +150 -0
- package/hooks/blocking/intent_only_ending_blocker.py +155 -0
- package/hooks/blocking/session_handoff_blocker.py +190 -0
- package/hooks/blocking/subprocess_budget_completeness.py +380 -0
- package/hooks/blocking/test_code_rules_enforcer_annotations.py +225 -0
- package/hooks/blocking/test_code_rules_enforcer_cap_meta.py +1 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_dataclass_field.py +467 -0
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body.py +330 -0
- package/hooks/blocking/test_code_rules_enforcer_duplicate_body_hook_routing.py +179 -0
- package/hooks/blocking/test_code_rules_enforcer_magic_slice_bounds.py +133 -0
- package/hooks/blocking/test_destructive_command_blocker.py +622 -3
- package/hooks/blocking/test_hook_prose_detector_consistency.py +265 -0
- package/hooks/blocking/test_intent_only_ending_blocker.py +175 -0
- package/hooks/blocking/test_session_handoff_blocker.py +312 -0
- package/hooks/blocking/test_subprocess_budget_completeness.py +588 -0
- package/hooks/blocking/test_workflow_substitution_slot_blocker.py +242 -0
- package/hooks/blocking/workflow_substitution_slot_blocker.py +159 -0
- package/hooks/hooks.json +25 -0
- package/hooks/hooks_constants/code_rules_enforcer_constants.py +16 -0
- package/hooks/hooks_constants/dead_dataclass_field_constants.py +25 -0
- package/hooks/hooks_constants/destructive_command_segment_constants.py +178 -0
- package/hooks/hooks_constants/duplicate_function_body_constants.py +17 -0
- package/hooks/hooks_constants/hook_prose_detector_consistency_constants.py +30 -0
- package/hooks/hooks_constants/messages.py +4 -0
- package/hooks/hooks_constants/session_handoff_blocker_constants.py +10 -0
- package/hooks/hooks_constants/subprocess_budget_completeness_constants.py +5 -0
- package/hooks/hooks_constants/workflow_substitution_slot_blocker_constants.py +22 -0
- package/hooks/workflow/auto_formatter.py +26 -1
- package/hooks/workflow/test_auto_formatter.py +134 -0
- package/package.json +1 -1
- package/rules/conservative-action.md +1 -0
- package/rules/docstring-prose-matches-implementation.md +43 -0
- package/rules/hook-prose-matches-detector.md +26 -0
- package/rules/long-horizon-autonomy.md +43 -0
- package/rules/no-inline-destructive-literals.md +11 -0
- package/rules/workflow-substitution-slots.md +7 -0
- package/skills/autoconverge/SKILL.md +68 -6
- package/skills/autoconverge/reference/closing-report.md +44 -0
- package/skills/autoconverge/reference/convergence.md +7 -3
- package/skills/autoconverge/reference/stop-conditions.md +7 -2
- package/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
- package/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +105 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +30 -1
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +265 -0
- package/skills/autoconverge/workflow/converge.mjs +106 -38
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -0
- package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +259 -0
- package/skills/autoconverge/workflow/render_report.py +903 -0
- package/skills/autoconverge/workflow/test_render_report.py +484 -0
- package/skills/pr-converge/scripts/check_convergence.py +195 -64
- package/skills/pr-converge/scripts/test_check_convergence.py +173 -2
- package/skills/update/SKILL.md +37 -5
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""Tests for session_handoff_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__), "session_handoff_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 session_handoff_blocker
|
|
18
|
+
from hooks_constants.messages import USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
19
|
+
|
|
20
|
+
NEW_SESSION_PROPOSAL_MESSAGE = (
|
|
21
|
+
"I recommend we continue this in a fresh session to keep things manageable."
|
|
22
|
+
)
|
|
23
|
+
RUNNING_LOW_ON_CONTEXT_MESSAGE = (
|
|
24
|
+
"We are running low on context, so let me summarize where things stand."
|
|
25
|
+
)
|
|
26
|
+
SHORT_ON_TOKENS_MESSAGE = "I'm getting short on tokens, so I'll wrap up here."
|
|
27
|
+
CONSERVE_CONTEXT_MESSAGE = "To conserve context, let me stop and hand off the remaining work."
|
|
28
|
+
CONTEXT_WINDOW_HANDOFF_MESSAGE = (
|
|
29
|
+
"The context window is filling up, so I'll wrap up and we can continue later."
|
|
30
|
+
)
|
|
31
|
+
BENIGN_TOPICAL_MESSAGE = "The function accepts a context manager and a token string."
|
|
32
|
+
CLEAN_MESSAGE = "The parser handles every fixture and returns a deduplicated list."
|
|
33
|
+
TECHNICAL_TERMINAL_SESSION_MESSAGE = (
|
|
34
|
+
"Consider starting a new session in your terminal to pick up the env vars."
|
|
35
|
+
)
|
|
36
|
+
LOAD_TEST_SESSION_MESSAGE = "We can spin up a fresh session for the load test."
|
|
37
|
+
DATABASE_SESSION_MESSAGE = "Open a new database session before running the query."
|
|
38
|
+
HANDOFF_NEW_SESSION_MESSAGE = (
|
|
39
|
+
"Let's wrap up and continue this in a fresh session to pick this up later."
|
|
40
|
+
)
|
|
41
|
+
LOW_ON_CONTEXT_WITHOUT_CUE_MESSAGE = (
|
|
42
|
+
"I am low on context for this edge case in the parser."
|
|
43
|
+
)
|
|
44
|
+
SAVE_TOKENS_REPORT_MESSAGE = "To save tokens, I inlined the constant."
|
|
45
|
+
LOW_ON_CONTEXT_WITH_HANDOFF_CUE_MESSAGE = (
|
|
46
|
+
"I'm low on context, so let me wrap up and hand off."
|
|
47
|
+
)
|
|
48
|
+
NEW_SESSION_TOKEN_MESSAGE = (
|
|
49
|
+
"To continue this task I need the new session token from the API."
|
|
50
|
+
)
|
|
51
|
+
RESUME_TRAFFIC_FRESH_SESSION_POOL_MESSAGE = (
|
|
52
|
+
"After deploy, resume traffic in a fresh session pool."
|
|
53
|
+
)
|
|
54
|
+
THIRD_PERSON_RUNS_LOW_DESIGN_MESSAGE = (
|
|
55
|
+
"The agent should stop when it runs low on context, per the old design."
|
|
56
|
+
)
|
|
57
|
+
DOCUMENT_RUNNING_LOW_MESSAGE = (
|
|
58
|
+
"Document that agents running low on tokens should not stop."
|
|
59
|
+
)
|
|
60
|
+
USER_ADVICE_NEW_SESSION_MESSAGE = (
|
|
61
|
+
"To save tokens, you could start a new session for the unrelated task."
|
|
62
|
+
)
|
|
63
|
+
CONTEXT_WINDOW_SUMMARIZE_MESSAGE = (
|
|
64
|
+
"The context window is getting full, so let me summarize where we are."
|
|
65
|
+
)
|
|
66
|
+
BENIGN_SUMMARIZE_REPORT_MESSAGE = "I should summarize the findings for the report."
|
|
67
|
+
BENIGN_STOP_PLUS_UNRELATED_RUNNING_LOW_MESSAGE = (
|
|
68
|
+
"Let me pause the animation timer. The job fails when it is running low on tokens."
|
|
69
|
+
)
|
|
70
|
+
BENIGN_STOP_PLUS_USER_DIRECTED_NEW_SESSION_MESSAGE = (
|
|
71
|
+
"Let me stop the timer. The user can continue this in a fresh session."
|
|
72
|
+
)
|
|
73
|
+
EMPTY_MESSAGE = ""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def run_hook_with_message(assistant_message: str) -> subprocess.CompletedProcess:
|
|
77
|
+
hook_input_payload = json.dumps({"last_assistant_message": assistant_message})
|
|
78
|
+
return subprocess.run(
|
|
79
|
+
[sys.executable, HOOK_SCRIPT_PATH],
|
|
80
|
+
input=hook_input_payload,
|
|
81
|
+
capture_output=True,
|
|
82
|
+
text=True,
|
|
83
|
+
check=False,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def run_hook_with_payload(hook_input_payload: dict) -> subprocess.CompletedProcess:
|
|
88
|
+
return subprocess.run(
|
|
89
|
+
[sys.executable, HOOK_SCRIPT_PATH],
|
|
90
|
+
input=json.dumps(hook_input_payload),
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
check=False,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_user_facing_notice_matches_config_messages_module():
|
|
98
|
+
config_messages_path = os.path.join(_HOOK_CONFIG_DIR, "messages.py")
|
|
99
|
+
specification = importlib.util.spec_from_file_location("messages", config_messages_path)
|
|
100
|
+
module = importlib.util.module_from_spec(specification)
|
|
101
|
+
specification.loader.exec_module(module)
|
|
102
|
+
|
|
103
|
+
assert module.USER_FACING_CONTEXT_REASSURANCE_NOTICE == USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
104
|
+
assert (
|
|
105
|
+
session_handoff_blocker.USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
106
|
+
== module.USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_new_session_proposal_emits_block_with_short_user_notice():
|
|
111
|
+
completed_process = run_hook_with_message(NEW_SESSION_PROPOSAL_MESSAGE)
|
|
112
|
+
|
|
113
|
+
assert completed_process.returncode == 0
|
|
114
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
115
|
+
|
|
116
|
+
assert parsed_response["decision"] == "block"
|
|
117
|
+
assert parsed_response["systemMessage"] == USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
118
|
+
assert parsed_response["suppressOutput"] is True
|
|
119
|
+
assert "ample context remaining" in parsed_response["reason"]
|
|
120
|
+
assert "long-horizon-autonomy" in parsed_response["reason"]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_running_low_on_context_emits_block():
|
|
124
|
+
completed_process = run_hook_with_message(RUNNING_LOW_ON_CONTEXT_MESSAGE)
|
|
125
|
+
|
|
126
|
+
assert completed_process.returncode == 0
|
|
127
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
128
|
+
|
|
129
|
+
assert parsed_response["decision"] == "block"
|
|
130
|
+
assert parsed_response["systemMessage"] == USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_short_on_tokens_emits_block():
|
|
134
|
+
completed_process = run_hook_with_message(SHORT_ON_TOKENS_MESSAGE)
|
|
135
|
+
|
|
136
|
+
assert completed_process.returncode == 0
|
|
137
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
138
|
+
|
|
139
|
+
assert parsed_response["decision"] == "block"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_conserve_context_emits_block():
|
|
143
|
+
completed_process = run_hook_with_message(CONSERVE_CONTEXT_MESSAGE)
|
|
144
|
+
|
|
145
|
+
assert completed_process.returncode == 0
|
|
146
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
147
|
+
|
|
148
|
+
assert parsed_response["decision"] == "block"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_context_window_co_occurring_handoff_cue_emits_block():
|
|
152
|
+
completed_process = run_hook_with_message(CONTEXT_WINDOW_HANDOFF_MESSAGE)
|
|
153
|
+
|
|
154
|
+
assert completed_process.returncode == 0
|
|
155
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
156
|
+
|
|
157
|
+
assert parsed_response["decision"] == "block"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_benign_topical_mention_passes_through_with_no_output():
|
|
161
|
+
completed_process = run_hook_with_message(BENIGN_TOPICAL_MESSAGE)
|
|
162
|
+
|
|
163
|
+
assert completed_process.returncode == 0
|
|
164
|
+
assert completed_process.stdout == ""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_technical_terminal_session_passes_through_with_no_output():
|
|
168
|
+
completed_process = run_hook_with_message(TECHNICAL_TERMINAL_SESSION_MESSAGE)
|
|
169
|
+
|
|
170
|
+
assert completed_process.returncode == 0
|
|
171
|
+
assert completed_process.stdout == ""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_load_test_session_passes_through_with_no_output():
|
|
175
|
+
completed_process = run_hook_with_message(LOAD_TEST_SESSION_MESSAGE)
|
|
176
|
+
|
|
177
|
+
assert completed_process.returncode == 0
|
|
178
|
+
assert completed_process.stdout == ""
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def test_database_session_passes_through_with_no_output():
|
|
182
|
+
completed_process = run_hook_with_message(DATABASE_SESSION_MESSAGE)
|
|
183
|
+
|
|
184
|
+
assert completed_process.returncode == 0
|
|
185
|
+
assert completed_process.stdout == ""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_new_session_with_handoff_framing_emits_block():
|
|
189
|
+
completed_process = run_hook_with_message(HANDOFF_NEW_SESSION_MESSAGE)
|
|
190
|
+
|
|
191
|
+
assert completed_process.returncode == 0
|
|
192
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
193
|
+
|
|
194
|
+
assert parsed_response["decision"] == "block"
|
|
195
|
+
assert parsed_response["systemMessage"] == USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_low_on_context_without_handoff_cue_passes_through_with_no_output():
|
|
199
|
+
completed_process = run_hook_with_message(LOW_ON_CONTEXT_WITHOUT_CUE_MESSAGE)
|
|
200
|
+
|
|
201
|
+
assert completed_process.returncode == 0
|
|
202
|
+
assert completed_process.stdout == ""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_save_tokens_work_report_passes_through_with_no_output():
|
|
206
|
+
completed_process = run_hook_with_message(SAVE_TOKENS_REPORT_MESSAGE)
|
|
207
|
+
|
|
208
|
+
assert completed_process.returncode == 0
|
|
209
|
+
assert completed_process.stdout == ""
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_low_on_context_with_handoff_cue_emits_block():
|
|
213
|
+
completed_process = run_hook_with_message(LOW_ON_CONTEXT_WITH_HANDOFF_CUE_MESSAGE)
|
|
214
|
+
|
|
215
|
+
assert completed_process.returncode == 0
|
|
216
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
217
|
+
|
|
218
|
+
assert parsed_response["decision"] == "block"
|
|
219
|
+
assert parsed_response["systemMessage"] == USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_new_session_token_passes_through_with_no_output():
|
|
223
|
+
completed_process = run_hook_with_message(NEW_SESSION_TOKEN_MESSAGE)
|
|
224
|
+
|
|
225
|
+
assert completed_process.returncode == 0
|
|
226
|
+
assert completed_process.stdout == ""
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_resume_traffic_fresh_session_pool_passes_through_with_no_output():
|
|
230
|
+
completed_process = run_hook_with_message(RESUME_TRAFFIC_FRESH_SESSION_POOL_MESSAGE)
|
|
231
|
+
|
|
232
|
+
assert completed_process.returncode == 0
|
|
233
|
+
assert completed_process.stdout == ""
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def test_third_person_runs_low_description_passes_through_with_no_output():
|
|
237
|
+
completed_process = run_hook_with_message(THIRD_PERSON_RUNS_LOW_DESIGN_MESSAGE)
|
|
238
|
+
|
|
239
|
+
assert completed_process.returncode == 0
|
|
240
|
+
assert completed_process.stdout == ""
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_document_running_low_instruction_passes_through_with_no_output():
|
|
244
|
+
completed_process = run_hook_with_message(DOCUMENT_RUNNING_LOW_MESSAGE)
|
|
245
|
+
|
|
246
|
+
assert completed_process.returncode == 0
|
|
247
|
+
assert completed_process.stdout == ""
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_user_directed_new_session_advice_passes_through_with_no_output():
|
|
251
|
+
completed_process = run_hook_with_message(USER_ADVICE_NEW_SESSION_MESSAGE)
|
|
252
|
+
|
|
253
|
+
assert completed_process.returncode == 0
|
|
254
|
+
assert completed_process.stdout == ""
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def test_context_window_summarize_handoff_emits_block():
|
|
258
|
+
completed_process = run_hook_with_message(CONTEXT_WINDOW_SUMMARIZE_MESSAGE)
|
|
259
|
+
|
|
260
|
+
assert completed_process.returncode == 0
|
|
261
|
+
parsed_response = json.loads(completed_process.stdout)
|
|
262
|
+
|
|
263
|
+
assert parsed_response["decision"] == "block"
|
|
264
|
+
assert parsed_response["systemMessage"] == USER_FACING_CONTEXT_REASSURANCE_NOTICE
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def test_benign_summarize_report_passes_through_with_no_output():
|
|
268
|
+
completed_process = run_hook_with_message(BENIGN_SUMMARIZE_REPORT_MESSAGE)
|
|
269
|
+
|
|
270
|
+
assert completed_process.returncode == 0
|
|
271
|
+
assert completed_process.stdout == ""
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def test_benign_stop_with_unrelated_running_low_sentence_passes_through_with_no_output():
|
|
275
|
+
completed_process = run_hook_with_message(
|
|
276
|
+
BENIGN_STOP_PLUS_UNRELATED_RUNNING_LOW_MESSAGE
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
assert completed_process.returncode == 0
|
|
280
|
+
assert completed_process.stdout == ""
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_benign_stop_with_user_directed_new_session_sentence_passes_through_with_no_output():
|
|
284
|
+
completed_process = run_hook_with_message(
|
|
285
|
+
BENIGN_STOP_PLUS_USER_DIRECTED_NEW_SESSION_MESSAGE
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
assert completed_process.returncode == 0
|
|
289
|
+
assert completed_process.stdout == ""
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_clean_message_passes_through_with_no_output():
|
|
293
|
+
completed_process = run_hook_with_message(CLEAN_MESSAGE)
|
|
294
|
+
|
|
295
|
+
assert completed_process.returncode == 0
|
|
296
|
+
assert completed_process.stdout == ""
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def test_empty_message_passes_through_with_no_output():
|
|
300
|
+
completed_process = run_hook_with_message(EMPTY_MESSAGE)
|
|
301
|
+
|
|
302
|
+
assert completed_process.returncode == 0
|
|
303
|
+
assert completed_process.stdout == ""
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def test_stop_hook_active_short_circuits_with_no_output():
|
|
307
|
+
completed_process = run_hook_with_payload(
|
|
308
|
+
{"last_assistant_message": NEW_SESSION_PROPOSAL_MESSAGE, "stop_hook_active": True}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
assert completed_process.returncode == 0
|
|
312
|
+
assert completed_process.stdout == ""
|