claude-dev-env 8.46.11 → 8.46.13
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/hooks/blocking/code_rules_shared.py +13 -3
- package/hooks/blocking/test_code_rules_shared.py +51 -3
- package/hooks/hooks_constants/thread_spawn_pace_hook_constants.py +2 -2
- package/hooks/routing/test_thread_spawn_pace_hook.py +1 -1
- package/hooks/routing/thread_spawn_pace_hook.py +3 -3
- package/package.json +1 -1
|
@@ -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 (
|
|
@@ -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
|
-
|
|
266
|
-
repository_file.parent.mkdir(parents=True)
|
|
265
|
+
repository_file_outside_temp_root = Path(WORKING_DIRECTORY).parent / "repository" / "orders.py"
|
|
267
266
|
|
|
268
|
-
assert
|
|
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
|
+
)
|
|
@@ -11,7 +11,7 @@ USAGE_PACE_SCRIPT_FILE_NAME = "usage_pace.py"
|
|
|
11
11
|
USAGE_PACE_TIMEOUT_SECONDS = 15
|
|
12
12
|
|
|
13
13
|
TOOL_INPUT_KEY = "tool_input"
|
|
14
|
-
THREAD_MODEL_WHEN_OVER_PACE = "opus"
|
|
14
|
+
THREAD_MODEL_WHEN_OVER_PACE = "claude-opus-5-5"
|
|
15
15
|
THREAD_EFFORT_WHEN_OVER_PACE = "low"
|
|
16
16
|
MODEL_INPUT_KEY = "model"
|
|
17
17
|
EFFORT_INPUT_KEY = "effort"
|
|
@@ -22,7 +22,7 @@ ADDITIONAL_CONTEXT_KEY = "additionalContext"
|
|
|
22
22
|
NOT_AN_OBJECT_REASON = "start_thread_session input is not a JSON object"
|
|
23
23
|
RESHAPE_CONTEXT_PREFIX = (
|
|
24
24
|
"Usage over pace or unreadable: this thread spawn now runs on "
|
|
25
|
-
"opus at low effort. Pace verdict: "
|
|
25
|
+
"claude-opus-5-5 at low effort. Pace verdict: "
|
|
26
26
|
)
|
|
27
27
|
PACE_VERDICT_CONTEXT_MAXIMUM_CHARACTERS = 600
|
|
28
28
|
LAUNCH_FAILURE_ERROR_TEMPLATE = "usage pace did not finish: {failure_type}"
|
|
@@ -8,7 +8,7 @@ beside it, or the script ``COORDINATOR_USAGE_PACE_SCRIPT`` names.
|
|
|
8
8
|
|
|
9
9
|
usage under pace (exit 1) -> no output; the call runs unchanged
|
|
10
10
|
over pace (exit 0) or unreadable -> updatedInput:
|
|
11
|
-
model "opus", effort "low"
|
|
11
|
+
model "claude-opus-5-5", effort "low"
|
|
12
12
|
input that cannot be reshaped -> deny with a one-line reason
|
|
13
13
|
|
|
14
14
|
``updatedInput`` is the whole tool input the call runs with, so it carries
|
|
@@ -65,13 +65,13 @@ class SpawnNotReshapable(Exception):
|
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def reshape_thread_spawn(tool_input: object) -> dict[str, object]:
|
|
68
|
-
"""Move a thread spawn to
|
|
68
|
+
"""Move a thread spawn to Opus 5.5 at low effort.
|
|
69
69
|
|
|
70
70
|
::
|
|
71
71
|
|
|
72
72
|
{"title": "t", "instructions": "Do X."}
|
|
73
73
|
-> {"title": "t", "instructions": "Do X.",
|
|
74
|
-
"model": "opus", "effort": "low"}
|
|
74
|
+
"model": "claude-opus-5-5", "effort": "low"}
|
|
75
75
|
|
|
76
76
|
Args:
|
|
77
77
|
tool_input: The ``tool_input`` object of the start_thread_session call.
|