claude-dev-env 8.46.12 → 8.46.14

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.
@@ -435,7 +435,9 @@ def is_under_session_scratchpad(file_path: str, hook_payload: dict) -> bool:
435
435
  """Return True when file_path resolves under the harness session scratchpad.
436
436
 
437
437
  One-off scripts written to the session scratchpad are throwaway tooling
438
- outside every repo, so the TDD and CODE_RULES gates skip them.
438
+ outside every repo, so the TDD and CODE_RULES gates skip them. A path at
439
+ or under the payload's working directory (``cwd``) is never scratch, so a
440
+ repository worktree checked out inside the scratchpad stays fully gated.
439
441
 
440
442
  The match keys on the session id — from the payload, or the
441
443
  ``CLAUDE_CODE_SESSION_ID`` environment variable — and on the temp-directory
@@ -446,10 +448,12 @@ def is_under_session_scratchpad(file_path: str, hook_payload: dict) -> bool:
446
448
 
447
449
  Args:
448
450
  file_path: The path the write targets.
449
- hook_payload: The PreToolUse payload carrying the session id.
451
+ hook_payload: The PreToolUse payload carrying the session id and the
452
+ working directory (``cwd``).
450
453
 
451
454
  Returns:
452
- True when file_path's real path sits at or under the session scratchpad.
455
+ True when file_path's real path sits at or under the session scratchpad
456
+ and outside the payload's working directory.
453
457
  """
454
458
  if not file_path:
455
459
  return False
@@ -457,6 +461,12 @@ def is_under_session_scratchpad(file_path: str, hook_payload: dict) -> bool:
457
461
  if not session_id:
458
462
  return False
459
463
  real_target = os.path.realpath(file_path)
464
+ working_directory = str(hook_payload.get("cwd") or "")
465
+ if working_directory and _is_at_or_under_root(
466
+ _normalized_absolute_comparison_path(real_target),
467
+ _normalized_absolute_comparison_path(os.path.realpath(working_directory)),
468
+ ):
469
+ return False
460
470
  real_temp_root = os.path.realpath(tempfile.gettempdir())
461
471
  all_relative_parts = _relative_parts_under_temp_root(real_target, real_temp_root)
462
472
  return (
@@ -5,9 +5,9 @@ hex color codes (``"#FFFFFF"``), URL fragments
5
5
  (``"https://x#section"``), and f-string interpolation patterns. None of
6
6
  those ``#`` characters belong to a comment token. ``check_comments_python``
7
7
  and the Python branch of ``extract_comment_texts`` route their ``#``
8
- detection through ``tokenize.generate_tokens`` so only true ``COMMENT``
8
+ detection through ``tokenize.generate_tokens`` so only ``COMMENT``
9
9
  tokens are considered. These tests pin both halves of that contract:
10
- ``#``-in-strings is exempt; real inline comments that land AFTER such
10
+ ``#``-in-strings is exempt; inline comments that land AFTER such
11
11
  a string still flag.
12
12
  """
13
13
 
@@ -262,10 +262,12 @@ def test_is_ephemeral_path_false_for_repository_file(
262
262
  ) -> None:
263
263
  _simulate_windows_platform(monkeypatch, tmp_path)
264
264
  monkeypatch.delenv("CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT", raising=False)
265
- repository_file = tmp_path / "repository" / "orders.py"
266
- repository_file.parent.mkdir(parents=True)
265
+ repository_file_outside_temp_root = Path(WORKING_DIRECTORY).parent / "repository" / "orders.py"
267
266
 
268
- assert _SHARED_MODULE.is_ephemeral_path(str(repository_file), _session_payload()) is False
267
+ assert (
268
+ _SHARED_MODULE.is_ephemeral_path(str(repository_file_outside_temp_root), _session_payload())
269
+ is False
270
+ )
269
271
 
270
272
 
271
273
  def test_is_ephemeral_path_reads_session_id_from_environment(
@@ -309,3 +311,49 @@ def test_normalized_path_marker_table_carries_no_backslash_entry(
309
311
  not the marker, so a backslash entry can never match."""
310
312
  marker_table = getattr(_SHARED_MODULE, normalized_marker_table_name)
311
313
  assert all("\\" not in each_marker for each_marker in marker_table)
314
+
315
+
316
+ def test_ephemeral_path_is_false_for_file_under_payload_cwd_inside_scratchpad(
317
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
318
+ ) -> None:
319
+ _point_temporary_directory_at(monkeypatch, tmp_path)
320
+ scratchpad_directory = _build_scratchpad_directory(tmp_path)
321
+ repository_module = scratchpad_directory / "orders.py"
322
+ payload_with_scratchpad_cwd = {"cwd": str(scratchpad_directory), "session_id": SESSION_ID}
323
+
324
+ assert (
325
+ _SHARED_MODULE.is_ephemeral_path(str(repository_module), payload_with_scratchpad_cwd)
326
+ is False
327
+ )
328
+
329
+
330
+ def test_scratchpad_is_false_for_worktree_under_payload_cwd(
331
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
332
+ ) -> None:
333
+ _point_temporary_directory_at(monkeypatch, tmp_path)
334
+ scratchpad_directory = _build_scratchpad_directory(tmp_path)
335
+ worktree_directory = scratchpad_directory / "worktree"
336
+ repository_module = worktree_directory / "src" / "orders.py"
337
+ payload_with_worktree_cwd = {"cwd": str(worktree_directory), "session_id": SESSION_ID}
338
+
339
+ assert (
340
+ _SHARED_MODULE.is_under_session_scratchpad(str(repository_module), payload_with_worktree_cwd)
341
+ is False
342
+ )
343
+
344
+
345
+ def test_ephemeral_path_stays_true_for_scratchpad_file_outside_payload_cwd(
346
+ tmp_path: Path, monkeypatch: pytest.MonkeyPatch
347
+ ) -> None:
348
+ _point_temporary_directory_at(monkeypatch, tmp_path)
349
+ scratchpad_directory = _build_scratchpad_directory(tmp_path)
350
+ throwaway_script = scratchpad_directory / "one_off_tool.py"
351
+ payload_with_worktree_cwd = {
352
+ "cwd": str(scratchpad_directory / "worktree"),
353
+ "session_id": SESSION_ID,
354
+ }
355
+
356
+ assert _SHARED_MODULE.is_ephemeral_path(str(throwaway_script), _session_payload()) is True
357
+ assert (
358
+ _SHARED_MODULE.is_ephemeral_path(str(throwaway_script), payload_with_worktree_cwd) is True
359
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.46.12",
3
+ "version": "8.46.14",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {