claude-dev-env 1.17.1 → 1.17.5

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.
@@ -1,261 +0,0 @@
1
- """Tests for prompt-workflow-stop-guard hook."""
2
-
3
- import json
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
-
8
- import pytest
9
-
10
-
11
- SCRIPT_PATH = Path(__file__).parent / "prompt-workflow-stop-guard.py"
12
-
13
-
14
- @pytest.fixture(autouse=True)
15
- def _disable_prompt_workflow_clipboard_in_subprocess(
16
- monkeypatch: pytest.MonkeyPatch,
17
- ) -> None:
18
- """Subprocess hook inherits env; clipboard would be flaky in CI."""
19
- monkeypatch.setenv("PROMPT_WORKFLOW_SKIP_CLIPBOARD", "1")
20
-
21
- def _run_hook(payload: dict) -> subprocess.CompletedProcess[str]:
22
- return subprocess.run(
23
- [sys.executable, str(SCRIPT_PATH)],
24
- input=json.dumps(payload),
25
- text=True,
26
- capture_output=True,
27
- check=False,
28
- )
29
-
30
- def _full_checklist_rows() -> str:
31
- return (
32
- "checklist_results:\n"
33
- "- structured_scoped_instructions\n"
34
- "- sequential_steps_present\n"
35
- "- positive_framing\n"
36
- "- acceptance_criteria_defined\n"
37
- "- safety_reversibility_language\n"
38
- "- reversible_action_and_safety_check_guidance\n"
39
- "- concrete_output_contract\n"
40
- "- scope_boundary_present\n"
41
- "- explicit_scope_anchors_present\n"
42
- "- all_instructions_artifact_bound\n"
43
- "- scope_terms_explicit_and_anchored\n"
44
- "- completion_boundary_measurable\n"
45
- "- citation_grounding_policy_present\n"
46
- "- source_priority_rules_present\n"
47
- "- artifact_language_confidence\n"
48
- )
49
-
50
- def test_blocks_internal_object_leak_without_debug_intent() -> None:
51
- payload = {
52
- "last_assistant_message": '{"pipeline_mode": "internal_section_refinement_with_final_audit"}',
53
- "last_user_message": "just return the final prompt",
54
- }
55
- result = _run_hook(payload)
56
- response = json.loads(result.stdout)
57
- assert response["decision"] == "block"
58
- assert "Raw internal refinement object leakage" in response["reason"]
59
-
60
- def test_allows_internal_object_with_debug_intent() -> None:
61
- payload = {
62
- "last_assistant_message": '{"pipeline_mode": "internal_section_refinement_with_final_audit"}',
63
- "last_user_message": "debug: show internal pipeline object",
64
- }
65
- result = _run_hook(payload)
66
- assert result.stdout.strip() == ""
67
-
68
- def test_blocks_missing_checklist_rows() -> None:
69
- payload = {
70
- "last_assistant_message": "overall_status: pass\nchecklist_results: structured_scoped_instructions",
71
- }
72
- result = _run_hook(payload)
73
- response = json.loads(result.stdout)
74
- assert response["decision"] == "block"
75
- assert "Deterministic checklist rows missing" in response["reason"]
76
-
77
- def test_allows_prompt_workflow_output_without_checklist_container() -> None:
78
- payload = {
79
- "last_assistant_message": (
80
- "overall_status: pass\n"
81
- "target_local_roots\n"
82
- "target_canonical_roots\n"
83
- "target_file_globs\n"
84
- "comparison_basis\n"
85
- "completion_boundary\n"
86
- "base_minimal_instruction_layer: true\n"
87
- "on_demand_skill_loading: true\n"
88
- ),
89
- }
90
- result = _run_hook(payload)
91
- assert result.stdout.strip() == ""
92
-
93
- def test_blocks_missing_context_control_signals() -> None:
94
- payload = {
95
- "last_assistant_message": (
96
- "overall_status: pass\n"
97
- + _full_checklist_rows()
98
- + "target_local_roots\n"
99
- + "target_canonical_roots\n"
100
- + "target_file_globs\n"
101
- + "comparison_basis\n"
102
- + "completion_boundary\n"
103
- + "base_minimal_instruction_layer: true\n"
104
- ),
105
- }
106
- result = _run_hook(payload)
107
- response = json.loads(result.stdout)
108
- assert response["decision"] == "block"
109
- assert "Runtime context-control preamble missing" in response["reason"]
110
- assert "on-demand skill loading" in response["reason"]
111
-
112
- def test_blocks_ambiguous_scope_phrasing() -> None:
113
- payload = {
114
- "last_assistant_message": (
115
- "overall_status: pass\n"
116
- + _full_checklist_rows()
117
- + "scope block includes target_local_roots target_canonical_roots "
118
- + "target_file_globs comparison_basis completion_boundary "
119
- + "base_minimal_instruction_layer: true\n"
120
- + "on_demand_skill_loading: true\n"
121
- + "and applies to this session."
122
- ),
123
- }
124
- result = _run_hook(payload)
125
- response = json.loads(result.stdout)
126
- assert response["decision"] == "block"
127
- assert "Ambiguous scope phrasing detected" in response["reason"]
128
-
129
- def _wrap_five_section_scaffold(inner_body: str) -> str:
130
- return (
131
- "<role>Test role sentence one.</role>\n"
132
- "<background>Test background sentence one.</background>\n"
133
- f"{inner_body}\n"
134
- "<constraints>Test constraints sentence one.</constraints>\n"
135
- "<output_format>Test output format sentence one.</output_format>\n"
136
- )
137
-
138
-
139
- def _build_prompt_workflow_message_with_fenced_xml(fenced_xml_body: str) -> str:
140
- return (
141
- "Audit: pass 15/15\n"
142
- "```xml\n"
143
- + fenced_xml_body
144
- + "\n```\n"
145
- "overall_status: pass\n"
146
- + _full_checklist_rows()
147
- + "target_local_roots\n"
148
- "target_canonical_roots\n"
149
- "target_file_globs\n"
150
- "comparison_basis\n"
151
- "completion_boundary\n"
152
- "base_minimal_instruction_layer: true\n"
153
- "on_demand_skill_loading: true\n"
154
- )
155
-
156
-
157
- def test_allows_positive_phrasing_inside_fenced_xml() -> None:
158
- fenced_content = _wrap_five_section_scaffold(
159
- "<instructions>Ensure all functions have explicit return types.</instructions>"
160
- )
161
- payload = {
162
- "last_assistant_message": _build_prompt_workflow_message_with_fenced_xml(fenced_content),
163
- }
164
- result = _run_hook(payload)
165
- assert result.stdout.strip() == ""
166
-
167
-
168
- BANNED_KEYWORD_TEST_CASES: list[tuple[str, str]] = [
169
- ("do_not", "<instructions>Do not leave return types implicit.</instructions>"),
170
- ("avoid", "<instructions>Avoid missing return types.</instructions>"),
171
- ("never", "<constraints>Never store credentials in plain text.</constraints>"),
172
- ("without", "<instructions>Deploy without running tests first.</instructions>"),
173
- ("prevent", "<constraints>Prevent unauthorized access to the API.</constraints>"),
174
- ("reject", "<constraints>Reject all unsigned commits.</constraints>"),
175
- ("cannot", "<constraints>The API cannot accept unauthenticated requests.</constraints>"),
176
- ("unless", "<constraints>Skip the build step unless the user explicitly approves.</constraints>"),
177
- ("must_not", "<constraints>The script must not produce duplicates.</constraints>"),
178
- ("must_never", "<constraints>You must never store credentials in environment variables.</constraints>"),
179
- ("instead_of", "<instructions>Use explicit types instead of implicit ones.</instructions>"),
180
- ("rather_than", "<constraints>Prefer explicit types rather than inferred ones.</constraints>"),
181
- ("as_opposed_to", "<instructions>Use Grid as opposed to floats for layout.</instructions>"),
182
- ]
183
-
184
-
185
- @pytest.mark.parametrize(
186
- ("banned_pattern_name", "fenced_xml_content"),
187
- BANNED_KEYWORD_TEST_CASES,
188
- ids=[each_case[0] for each_case in BANNED_KEYWORD_TEST_CASES],
189
- )
190
- def test_blocks_banned_pattern_inside_fenced_xml(
191
- banned_pattern_name: str,
192
- fenced_xml_content: str,
193
- ) -> None:
194
- payload = {
195
- "last_assistant_message": _build_prompt_workflow_message_with_fenced_xml(
196
- _wrap_five_section_scaffold(fenced_xml_content)
197
- ),
198
- }
199
- result = _run_hook(payload)
200
- response = json.loads(result.stdout)
201
- assert response["decision"] == "block"
202
-
203
-
204
- def test_permits_negative_keywords_outside_fenced_xml() -> None:
205
- fenced_inner = _wrap_five_section_scaffold(
206
- "<instructions>Ensure all functions have explicit return types.</instructions>"
207
- )
208
- message = (
209
- "Audit: pass 15/15\n"
210
- "Do not skip the audit line.\n"
211
- "```xml\n"
212
- + fenced_inner
213
- + "\n```\n"
214
- "overall_status: pass\n"
215
- + _full_checklist_rows()
216
- + "target_local_roots\n"
217
- "target_canonical_roots\n"
218
- "target_file_globs\n"
219
- "comparison_basis\n"
220
- "completion_boundary\n"
221
- "base_minimal_instruction_layer: true\n"
222
- "on_demand_skill_loading: true\n"
223
- )
224
- payload = {"last_assistant_message": message}
225
- result = _run_hook(payload)
226
- assert result.stdout.strip() == ""
227
-
228
-
229
- def test_blocks_when_fenced_xml_missing_background_section() -> None:
230
- fenced_body = (
231
- "<role>Test role sentence one.</role>\n"
232
- "<instructions>Test instructions sentence one.</instructions>\n"
233
- "<constraints>Test constraints sentence one.</constraints>\n"
234
- "<output_format>Test output format sentence one.</output_format>\n"
235
- )
236
- payload = {
237
- "last_assistant_message": _build_prompt_workflow_message_with_fenced_xml(fenced_body),
238
- }
239
- result = _run_hook(payload)
240
- response = json.loads(result.stdout)
241
- assert response["decision"] == "block"
242
- assert "background" in response["reason"]
243
- assert "include all required XML sections" in response["systemMessage"]
244
-
245
-
246
- def test_allows_fully_structured_prompt_workflow_output() -> None:
247
- payload = {
248
- "last_assistant_message": (
249
- "overall_status: pass\n"
250
- + _full_checklist_rows()
251
- + "target_local_roots\n"
252
- + "target_canonical_roots\n"
253
- + "target_file_globs\n"
254
- + "comparison_basis\n"
255
- + "completion_boundary\n"
256
- + "base_minimal_instruction_layer: true\n"
257
- + "on_demand_skill_loading: true\n"
258
- ),
259
- }
260
- result = _run_hook(payload)
261
- assert result.stdout.strip() == ""