claude-dev-env 1.69.0 → 1.69.2

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.
@@ -0,0 +1,383 @@
1
+ """Ephemeral-path exemption tests for code_rules_enforcer and the classifier."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import json
7
+ import os
8
+ import subprocess
9
+ import sys
10
+ import tempfile
11
+ from pathlib import Path
12
+ from types import SimpleNamespace
13
+
14
+ import pytest
15
+
16
+ _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
17
+ _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
18
+ if _BLOCKING_DIRECTORY not in sys.path:
19
+ sys.path.insert(0, _BLOCKING_DIRECTORY)
20
+ if _HOOKS_DIRECTORY not in sys.path:
21
+ sys.path.insert(0, _HOOKS_DIRECTORY)
22
+
23
+ from code_rules_enforcer import main as enforcer_main # noqa: E402
24
+ from code_rules_shared import is_ephemeral_script_path # noqa: E402
25
+
26
+ _ENFORCER_SCRIPT = Path(__file__).resolve().parent / "code_rules_enforcer.py"
27
+ _TDD_SCRIPT = Path(__file__).resolve().parent / "tdd_enforcer.py"
28
+
29
+ _VIOLATING_PRODUCTION_SOURCE = "def process_data(payload: str) -> None:\n print(payload)\n"
30
+
31
+ code_rules_enforcer_module = SimpleNamespace(main=enforcer_main, sys=sys)
32
+
33
+
34
+ def _run_enforcer_cli(
35
+ all_cli_arguments: list[str],
36
+ extra_env: dict[str, str],
37
+ ) -> subprocess.CompletedProcess[str]:
38
+ """Drive the enforcer script through its real argv entry point.
39
+
40
+ Args:
41
+ all_cli_arguments: The argument vector appended after the script path.
42
+ extra_env: Additional environment variables merged into the subprocess env.
43
+
44
+ Returns:
45
+ The completed process carrying stdout, stderr, and the exit code.
46
+ """
47
+ subprocess_env = {**os.environ, **extra_env}
48
+ return subprocess.run(
49
+ [sys.executable, str(_ENFORCER_SCRIPT), *all_cli_arguments],
50
+ input="",
51
+ capture_output=True,
52
+ text=True,
53
+ check=False,
54
+ env=subprocess_env,
55
+ )
56
+
57
+
58
+ def _run_main_with_write_payload(
59
+ file_path: str,
60
+ content: str,
61
+ monkeypatch: pytest.MonkeyPatch,
62
+ capsys: pytest.CaptureFixture[str],
63
+ ) -> tuple[str, int]:
64
+ """Drive enforcer_main through its stdin entry point for a Write payload.
65
+
66
+ Args:
67
+ file_path: The destination path the Write targets.
68
+ content: The content of the Write.
69
+ monkeypatch: The pytest fixture used to redirect sys.stdin.
70
+ capsys: The pytest fixture used to capture the deny payload on stdout.
71
+
72
+ Returns:
73
+ A tuple of (captured_stdout, exit_code).
74
+ """
75
+ write_payload = json.dumps(
76
+ {
77
+ "tool_name": "Write",
78
+ "tool_input": {"file_path": file_path, "content": content},
79
+ }
80
+ )
81
+ monkeypatch.setattr(code_rules_enforcer_module.sys, "stdin", io.StringIO(write_payload))
82
+ exit_code = 0
83
+ try:
84
+ code_rules_enforcer_module.main([])
85
+ except SystemExit as each_exit:
86
+ exit_code = int(each_exit.code or 0)
87
+ captured = capsys.readouterr()
88
+ return captured.out, exit_code
89
+
90
+
91
+ def _run_tdd_with_write_payload(
92
+ file_path: str,
93
+ content: str,
94
+ ) -> subprocess.CompletedProcess[str]:
95
+ """Drive the TDD enforcer through subprocess with a Write payload.
96
+
97
+ Args:
98
+ file_path: The destination path the Write targets.
99
+ content: The production-looking content to write.
100
+
101
+ Returns:
102
+ The completed process carrying stdout, stderr, and the exit code.
103
+ """
104
+ write_payload = json.dumps(
105
+ {
106
+ "tool_name": "Write",
107
+ "tool_input": {"file_path": file_path, "content": content},
108
+ }
109
+ )
110
+ return subprocess.run(
111
+ [sys.executable, str(_TDD_SCRIPT)],
112
+ input=write_payload,
113
+ capture_output=True,
114
+ text=True,
115
+ check=False,
116
+ )
117
+
118
+
119
+ def _decision_from(completed: subprocess.CompletedProcess[str]) -> str | None:
120
+ """Extract the permissionDecision from a hook's JSON stdout.
121
+
122
+ Args:
123
+ completed: The completed subprocess carrying the hook's stdout.
124
+
125
+ Returns:
126
+ The permissionDecision string, or None when stdout is empty.
127
+ """
128
+ if not completed.stdout:
129
+ return None
130
+ parsed = json.loads(completed.stdout)
131
+ hook_output = parsed.get("hookSpecificOutput", {})
132
+ return hook_output.get("permissionDecision")
133
+
134
+
135
+ def test_should_return_true_for_claude_job_dir_tmp_path(
136
+ tmp_path: Path,
137
+ monkeypatch: pytest.MonkeyPatch,
138
+ ) -> None:
139
+ """B1: classifier returns True for a path under $CLAUDE_JOB_DIR/tmp."""
140
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
141
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path))
142
+ scratch_path = str(tmp_path / "tmp" / "scratch.py")
143
+ assert is_ephemeral_script_path(scratch_path) is True
144
+
145
+
146
+ def test_should_return_false_for_os_tempfile_gettempdir_root(
147
+ monkeypatch: pytest.MonkeyPatch,
148
+ ) -> None:
149
+ """B2: the shared OS temp directory is not an ephemeral source.
150
+
151
+ pytest sandbox fixtures live under tempfile.gettempdir(); matching it
152
+ would exempt the suite's own enforcer test targets.
153
+ """
154
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
155
+ monkeypatch.delenv("CLAUDE_JOB_DIR", raising=False)
156
+ system_temp_root = tempfile.gettempdir()
157
+ scratch_path = str(Path(system_temp_root) / "scratch_work.py")
158
+ assert is_ephemeral_script_path(scratch_path) is False
159
+
160
+
161
+ @pytest.mark.parametrize("env_name", ["TMPDIR", "TEMP", "TMP"])
162
+ def test_should_return_false_for_tmpdir_temp_tmp_env_roots(
163
+ env_name: str,
164
+ tmp_path: Path,
165
+ monkeypatch: pytest.MonkeyPatch,
166
+ ) -> None:
167
+ """B3: $TMPDIR / $TEMP / $TMP are not ephemeral sources.
168
+
169
+ A path under one of these env roots that is neither $CLAUDE_JOB_DIR/tmp
170
+ nor root-anchored /tmp must classify False.
171
+ """
172
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
173
+ monkeypatch.delenv("CLAUDE_JOB_DIR", raising=False)
174
+ monkeypatch.setenv(env_name, str(tmp_path / "env_root"))
175
+ scratch_path = str(tmp_path / "env_root" / "scratch.py")
176
+ assert is_ephemeral_script_path(scratch_path) is False
177
+
178
+
179
+ def test_should_return_false_for_pytest_tmp_path_when_job_dir_elsewhere(
180
+ tmp_path: Path,
181
+ monkeypatch: pytest.MonkeyPatch,
182
+ ) -> None:
183
+ """Regression guard: a pytest tmp_path under %TEMP% returns False.
184
+
185
+ This is the exact path class that broke the enforcer suite when the OS
186
+ temp directory was an ephemeral source: pytest tmp_path fixtures live
187
+ under tempfile.gettempdir(), so the enforcer's own test targets must not
188
+ classify as ephemeral. CLAUDE_JOB_DIR points elsewhere (its scratch dir
189
+ lives under .claude-ev/jobs, not %TEMP%).
190
+ """
191
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
192
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path / "elsewhere"))
193
+ pytest_sandbox_target = str(tmp_path / "candidate.py")
194
+ assert is_ephemeral_script_path(pytest_sandbox_target) is False
195
+
196
+
197
+ @pytest.mark.parametrize(
198
+ "raw_path",
199
+ [
200
+ "/tmp/scratch.py",
201
+ "/temp/scratch.py",
202
+ "C:/Temp/scratch.py",
203
+ "c:/tmp/scratch.py",
204
+ ],
205
+ )
206
+ def test_should_return_true_for_root_anchored_tmp_and_temp(
207
+ raw_path: str,
208
+ monkeypatch: pytest.MonkeyPatch,
209
+ ) -> None:
210
+ """B4: classifier returns True for root-anchored /tmp and /temp paths."""
211
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
212
+ assert is_ephemeral_script_path(raw_path) is True
213
+
214
+
215
+ @pytest.mark.parametrize(
216
+ "lookalike_path",
217
+ [
218
+ "/repo/tmp_helper.py",
219
+ "/repo/temp/foo.py",
220
+ "/repo/src/temperature.py",
221
+ "/repo/contemporary/x.py",
222
+ ],
223
+ )
224
+ def test_should_return_false_for_lookalike_tmp_temp_paths(
225
+ lookalike_path: str,
226
+ monkeypatch: pytest.MonkeyPatch,
227
+ ) -> None:
228
+ """B5: classifier returns False for paths that merely contain tmp/temp substrings."""
229
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
230
+ assert is_ephemeral_script_path(lookalike_path) is False
231
+
232
+
233
+ def test_should_resolve_relative_path_before_classifying(
234
+ tmp_path: Path,
235
+ monkeypatch: pytest.MonkeyPatch,
236
+ ) -> None:
237
+ """B6: a relative path resolving under $CLAUDE_JOB_DIR/tmp returns True."""
238
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
239
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path))
240
+ (tmp_path / "tmp").mkdir(parents=True, exist_ok=True)
241
+ monkeypatch.chdir(tmp_path / "tmp")
242
+ assert is_ephemeral_script_path("scratch.py") is True
243
+
244
+
245
+ def test_should_return_false_when_job_dir_unset(
246
+ monkeypatch: pytest.MonkeyPatch,
247
+ ) -> None:
248
+ """B7: with CLAUDE_JOB_DIR unset, a non-temp path returns False."""
249
+ monkeypatch.delenv("CLAUDE_JOB_DIR", raising=False)
250
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
251
+ assert is_ephemeral_script_path("/repo/src/orders.py") is False
252
+
253
+
254
+ @pytest.mark.parametrize("truthy_value", ["1", "true", "yes", "on"])
255
+ def test_should_return_false_when_disable_override_truthy(
256
+ truthy_value: str,
257
+ monkeypatch: pytest.MonkeyPatch,
258
+ ) -> None:
259
+ """B8: override set to each truthy value returns False even for a temp path."""
260
+ monkeypatch.setenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", truthy_value)
261
+ assert is_ephemeral_script_path("/tmp/scratch.py") is False
262
+
263
+
264
+ def test_should_classify_nonexistent_path_without_error(
265
+ monkeypatch: pytest.MonkeyPatch,
266
+ ) -> None:
267
+ """B9: a path that does not exist is classified by string, with no exception."""
268
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
269
+ classification_result = is_ephemeral_script_path("/tmp/does_not_exist.py")
270
+ assert isinstance(classification_result, bool)
271
+ assert classification_result is True
272
+
273
+
274
+ def test_should_return_false_for_empty_path() -> None:
275
+ """B10: an empty string returns False."""
276
+ assert is_ephemeral_script_path("") is False
277
+
278
+
279
+ def test_should_exit_zero_for_ephemeral_pretooluse_target(
280
+ tmp_path: Path,
281
+ monkeypatch: pytest.MonkeyPatch,
282
+ capsys: pytest.CaptureFixture[str],
283
+ ) -> None:
284
+ """B11: enforcer main exits 0 with no deny payload for an ephemeral file_path."""
285
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path))
286
+ ephemeral_path = str(tmp_path / "tmp" / "scratch.py")
287
+ captured_stdout, exit_code = _run_main_with_write_payload(
288
+ ephemeral_path,
289
+ _VIOLATING_PRODUCTION_SOURCE,
290
+ monkeypatch,
291
+ capsys,
292
+ )
293
+ assert exit_code == 0
294
+ assert "deny" not in captured_stdout.lower()
295
+
296
+
297
+ def test_should_exit_zero_for_ephemeral_path_with_hooks_substring(
298
+ tmp_path: Path,
299
+ monkeypatch: pytest.MonkeyPatch,
300
+ capsys: pytest.CaptureFixture[str],
301
+ ) -> None:
302
+ """B12: an ephemeral path carrying /hooks/ exits 0 (hook-infra route short-circuited)."""
303
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path))
304
+ ephemeral_hooks_path = str(tmp_path / "tmp" / "hooks" / "scratch.py")
305
+ captured_stdout, exit_code = _run_main_with_write_payload(
306
+ ephemeral_hooks_path,
307
+ _VIOLATING_PRODUCTION_SOURCE,
308
+ monkeypatch,
309
+ capsys,
310
+ )
311
+ assert exit_code == 0
312
+ assert "deny" not in captured_stdout.lower()
313
+
314
+
315
+ def test_should_return_zero_from_precheck_for_ephemeral_target(
316
+ tmp_path: Path,
317
+ ) -> None:
318
+ """B13: enforcer CLI returns 0 for an ephemeral root-anchored /tmp --as target."""
319
+ candidate_file = tmp_path / "candidate.py"
320
+ candidate_file.write_text(_VIOLATING_PRODUCTION_SOURCE, encoding="utf-8")
321
+ ephemeral_target = "/tmp/target.py"
322
+ completed = _run_enforcer_cli(["--check", str(candidate_file), "--as", ephemeral_target], extra_env={})
323
+ assert completed.returncode == 0
324
+
325
+
326
+ def test_should_run_full_suite_for_non_ephemeral_target(
327
+ tmp_path: Path,
328
+ ) -> None:
329
+ """B14: a non-ephemeral violating path still produces a deny payload."""
330
+ candidate_file = tmp_path / "candidate.py"
331
+ candidate_file.write_text(_VIOLATING_PRODUCTION_SOURCE, encoding="utf-8")
332
+ non_ephemeral_target = "/repo/src/orders.py"
333
+ completed = _run_enforcer_cli(
334
+ ["--check", str(candidate_file), "--as", non_ephemeral_target],
335
+ extra_env={},
336
+ )
337
+ assert completed.returncode == 1
338
+
339
+
340
+ def test_should_run_full_suite_when_override_truthy(
341
+ tmp_path: Path,
342
+ ) -> None:
343
+ """B15: with override set, an ephemeral violating path produces a deny."""
344
+ candidate_file = tmp_path / "candidate.py"
345
+ candidate_file.write_text(_VIOLATING_PRODUCTION_SOURCE, encoding="utf-8")
346
+ ephemeral_target = "/tmp/scratch.py"
347
+ completed = _run_enforcer_cli(
348
+ ["--check", str(candidate_file), "--as", ephemeral_target],
349
+ extra_env={"CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT": "1"},
350
+ )
351
+ assert completed.returncode == 1
352
+
353
+
354
+ def test_should_exempt_same_path_set_on_both_gates(
355
+ tmp_path: Path,
356
+ monkeypatch: pytest.MonkeyPatch,
357
+ capsys: pytest.CaptureFixture[str],
358
+ ) -> None:
359
+ """B21: every ephemeral path exits 0 on both enforcer main and TDD enforcer main."""
360
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path))
361
+ all_ephemeral_paths = [
362
+ str(tmp_path / "tmp" / "scratch.py"),
363
+ "/tmp/scratch.py",
364
+ "/temp/scratch.py",
365
+ ]
366
+ for each_ephemeral_path in all_ephemeral_paths:
367
+ captured_stdout, exit_code = _run_main_with_write_payload(
368
+ each_ephemeral_path,
369
+ _VIOLATING_PRODUCTION_SOURCE,
370
+ monkeypatch,
371
+ capsys,
372
+ )
373
+ assert exit_code == 0, (
374
+ f"enforcer must exit 0 for ephemeral path {each_ephemeral_path!r}, "
375
+ f"got exit_code={exit_code}, stdout={captured_stdout!r}"
376
+ )
377
+ assert "deny" not in captured_stdout.lower(), (
378
+ f"enforcer must not deny ephemeral path {each_ephemeral_path!r}"
379
+ )
380
+ completed = _run_tdd_with_write_payload(each_ephemeral_path, _VIOLATING_PRODUCTION_SOURCE)
381
+ assert _decision_from(completed) != "deny", (
382
+ f"TDD enforcer must not deny ephemeral path {each_ephemeral_path!r}"
383
+ )
@@ -1,6 +1,7 @@
1
1
  """Tests for tdd-enforcer hook (blocking behavior)."""
2
2
 
3
3
  import importlib.util
4
+ import io
4
5
  import json
5
6
  import os
6
7
  import subprocess
@@ -8,6 +9,7 @@ import sys
8
9
  import time
9
10
  from pathlib import Path
10
11
 
12
+ import pytest
11
13
 
12
14
  SCRIPT_PATH = Path(__file__).parent / "tdd_enforcer.py"
13
15
 
@@ -25,13 +27,18 @@ FRESHNESS_SECONDS = _PRODUCTION_MODULE._freshness_seconds()
25
27
  STALE_MTIME_OFFSET_SECONDS = FRESHNESS_SECONDS + 60
26
28
 
27
29
 
28
- def _run_hook_with_payload(payload: dict) -> subprocess.CompletedProcess[str]:
30
+ def _run_hook_with_payload(
31
+ payload: dict,
32
+ extra_env: dict[str, str] | None = None,
33
+ ) -> subprocess.CompletedProcess[str]:
34
+ subprocess_env = {**os.environ, **(extra_env or {})}
29
35
  return subprocess.run(
30
36
  [sys.executable, str(SCRIPT_PATH)],
31
37
  input=json.dumps(payload),
32
38
  text=True,
33
39
  capture_output=True,
34
40
  check=False,
41
+ env=subprocess_env,
35
42
  )
36
43
 
37
44
 
@@ -810,6 +817,104 @@ def test_should_deny_edit_that_swaps_an_import_target(tmp_path: Path) -> None:
810
817
  assert _decision_from(completed) == "deny"
811
818
 
812
819
 
820
+ def _run_hook_with_payload_and_env(
821
+ payload: dict,
822
+ extra_env: dict[str, str],
823
+ ) -> subprocess.CompletedProcess[str]:
824
+ return subprocess.run(
825
+ [sys.executable, str(SCRIPT_PATH)],
826
+ input=json.dumps(payload),
827
+ text=True,
828
+ capture_output=True,
829
+ check=False,
830
+ env={**os.environ, **extra_env},
831
+ )
832
+
833
+
834
+ _BEHAVIOR_BEARING_CONTENT = "def fulfill_order(order: str) -> str:\n return order\n"
835
+
836
+
837
+ def test_should_exit_zero_for_ephemeral_scratch_python() -> None:
838
+ """B16: behavior-bearing scratch .py under root-anchored /tmp exits 0 with no deny."""
839
+ ephemeral_path = "/tmp/scratch_work.py"
840
+ payload = _make_write_payload(Path(ephemeral_path), _BEHAVIOR_BEARING_CONTENT)
841
+ completed = _run_hook_with_payload(payload)
842
+ assert _decision_from(completed) != "deny", (
843
+ f"TDD enforcer must not deny ephemeral Python path, got: {completed.stdout!r}"
844
+ )
845
+
846
+
847
+ def test_should_exit_zero_for_ephemeral_scratch_typescript() -> None:
848
+ """B17: ephemeral .ts scratch file under root-anchored /tmp exits 0 with no deny."""
849
+ ephemeral_path = "/tmp/scratch_work.ts"
850
+ payload = {
851
+ "tool_name": "Write",
852
+ "tool_input": {
853
+ "file_path": ephemeral_path,
854
+ "content": "export function fulfillOrder(order: string): string { return order; }\n",
855
+ },
856
+ }
857
+ completed = _run_hook_with_payload(payload)
858
+ assert _decision_from(completed) != "deny", (
859
+ f"TDD enforcer must not deny ephemeral TypeScript path, got: {completed.stdout!r}"
860
+ )
861
+
862
+
863
+ def test_should_still_deny_non_ephemeral_python_without_test() -> None:
864
+ """B18: a non-ephemeral .py file with no matching test still receives a deny."""
865
+ non_ephemeral_path = "/repo/src/orders.py"
866
+ payload = _make_write_payload(Path(non_ephemeral_path), _BEHAVIOR_BEARING_CONTENT)
867
+ completed = _run_hook_with_payload(payload)
868
+ assert _decision_from(completed) == "deny", (
869
+ f"TDD enforcer must deny non-ephemeral production file, got: {completed.stdout!r}"
870
+ )
871
+
872
+
873
+ def test_should_deny_ephemeral_scratch_when_override_truthy() -> None:
874
+ """B19: with override set, an ephemeral scratch .py with no test still receives a deny."""
875
+ ephemeral_path = "/tmp/scratch_work.py"
876
+ payload = _make_write_payload(Path(ephemeral_path), _BEHAVIOR_BEARING_CONTENT)
877
+ completed = _run_hook_with_payload_and_env(
878
+ payload,
879
+ extra_env={"CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT": "1"},
880
+ )
881
+ assert _decision_from(completed) == "deny", (
882
+ f"TDD enforcer must deny ephemeral path when override is set, got: {completed.stdout!r}"
883
+ )
884
+
885
+
886
+ def test_should_exit_before_fresh_test_lookup_for_ephemeral(
887
+ tmp_path: Path,
888
+ monkeypatch: pytest.MonkeyPatch,
889
+ ) -> None:
890
+ """B20: the ephemeral exit precedes the fresh-test candidate search.
891
+
892
+ Spies on candidate_test_paths_for and asserts it is never reached for a
893
+ scratch .py under $CLAUDE_JOB_DIR/tmp, proving the early exit short-circuits
894
+ before any test-file lookup runs.
895
+ """
896
+ monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
897
+ monkeypatch.setenv("CLAUDE_JOB_DIR", str(tmp_path))
898
+ candidate_search_call_count = {"count": 0}
899
+
900
+ def _spy_candidate_test_paths_for(production_path: Path) -> list[Path]:
901
+ candidate_search_call_count["count"] += 1
902
+ return []
903
+
904
+ monkeypatch.setattr(
905
+ _PRODUCTION_MODULE, "candidate_test_paths_for", _spy_candidate_test_paths_for
906
+ )
907
+ scratch_path = str(tmp_path / "tmp" / "scratch_work.py")
908
+ payload = _make_write_payload(Path(scratch_path), _BEHAVIOR_BEARING_CONTENT)
909
+ monkeypatch.setattr(_PRODUCTION_MODULE.sys, "stdin", io.StringIO(json.dumps(payload)))
910
+ with pytest.raises(SystemExit) as raised_exit:
911
+ _PRODUCTION_MODULE.main()
912
+ assert int(raised_exit.value.code or 0) == 0
913
+ assert candidate_search_call_count["count"] == 0, (
914
+ "candidate_test_paths_for must not run for an ephemeral scratch path"
915
+ )
916
+
917
+
813
918
  def test_should_allow_edit_that_removes_an_import_statement(tmp_path: Path) -> None:
814
919
  sandbox = _sandbox(tmp_path)
815
920
  production_module = sandbox / "orders.py"
@@ -17,6 +17,12 @@ ALL_JAVASCRIPT_EXTENSIONS = {".js", ".ts", ".tsx", ".jsx"}
17
17
  ALL_CODE_EXTENSIONS = ALL_PYTHON_EXTENSIONS | ALL_JAVASCRIPT_EXTENSIONS
18
18
 
19
19
  ALL_TEST_PATH_PATTERNS = {"test_", "_test.", ".test.", ".spec.", "/tests/", "\\tests\\", "/tests.py", "\\tests.py"}
20
+ ALL_ROOT_ANCHORED_EPHEMERAL_DIRECTORIES: tuple[str, str] = ("/tmp", "/temp")
21
+ CLAUDE_JOB_DIR_ENVIRONMENT_VARIABLE_NAME: str = "CLAUDE_JOB_DIR"
22
+ CLAUDE_JOB_DIR_SCRATCH_SUBDIRECTORY: str = "tmp"
23
+ EPHEMERAL_EXEMPT_DISABLE_ENVIRONMENT_VARIABLE_NAME: str = "CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT"
24
+ ALL_EPHEMERAL_EXEMPT_DISABLE_TRUTHY_VALUES: frozenset[str] = frozenset({"1", "true", "yes", "on"})
25
+ LEADING_DRIVE_LETTER_PATTERN: re.Pattern[str] = re.compile(r"^[a-z]:")
20
26
  ALL_HOOK_INFRASTRUCTURE_PATTERNS = {"/.claude/hooks/", "\\.claude\\hooks\\", "\\.claude/hooks/", "/packages/claude-dev-env/hooks/", "\\packages\\claude-dev-env\\hooks\\"}
21
27
  ALL_WORKFLOW_REGISTRY_PATTERNS = {"/workflow/", "\\workflow\\", "_tab.py", "/states.py", "\\states.py", "/modules.py", "\\modules.py"}
22
28
  ALL_MIGRATION_PATH_PATTERNS = {"/migrations/", "\\migrations\\"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.69.0",
3
+ "version": "1.69.2",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {