claude-dev-env 8.96.7 → 8.97.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.
@@ -0,0 +1,98 @@
1
+ """Behavior tests for the plugin session baseline record."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ import plugin_session_baseline
11
+
12
+ PLUGIN_ID = "pstack@pstack-claude"
13
+
14
+
15
+ @pytest.fixture(autouse=True)
16
+ def isolated_temp_folder(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
17
+ monkeypatch.setattr(
18
+ plugin_session_baseline.tempfile, "gettempdir", lambda: str(tmp_path)
19
+ )
20
+
21
+
22
+ def test_installed_plugins_path_should_follow_the_config_directory(
23
+ monkeypatch: pytest.MonkeyPatch, tmp_path: Path
24
+ ) -> None:
25
+ monkeypatch.setenv("CLAUDE_CONFIG_DIR", str(tmp_path))
26
+ assert plugin_session_baseline.installed_plugins_path() == (
27
+ tmp_path / "plugins" / "installed_plugins.json"
28
+ )
29
+
30
+
31
+ def test_baseline_record_path_should_name_the_session_in_the_temp_folder(
32
+ tmp_path: Path,
33
+ ) -> None:
34
+ assert plugin_session_baseline.baseline_record_path("a/b c") == (
35
+ tmp_path / "plugin-session-baseline-abc.json"
36
+ )
37
+
38
+
39
+ def test_current_plugin_snapshot_should_keep_only_version_and_install_path(
40
+ tmp_path: Path,
41
+ ) -> None:
42
+ record_path = tmp_path / "installed_plugins.json"
43
+ record_path.write_text(
44
+ json.dumps(
45
+ {
46
+ "plugins": {
47
+ PLUGIN_ID: [
48
+ {
49
+ "scope": "user",
50
+ "installPath": "/cache/pstack/1.0.0",
51
+ "version": "1.0.0",
52
+ "lastUpdated": "2026-10-21T21:26:12.000Z",
53
+ }
54
+ ]
55
+ }
56
+ }
57
+ ),
58
+ encoding="utf-8",
59
+ )
60
+ assert plugin_session_baseline.current_plugin_snapshot(record_path) == {
61
+ PLUGIN_ID: [{"version": "1.0.0", "installPath": "/cache/pstack/1.0.0"}]
62
+ }
63
+
64
+
65
+ def test_current_plugin_snapshot_should_read_a_malformed_record_as_empty(
66
+ tmp_path: Path,
67
+ ) -> None:
68
+ record_path = tmp_path / "installed_plugins.json"
69
+ record_path.write_text("{not json", encoding="utf-8")
70
+ assert plugin_session_baseline.current_plugin_snapshot(record_path) == {}
71
+
72
+
73
+ def test_read_baseline_record_should_return_what_the_writer_stored() -> None:
74
+ snapshot = {PLUGIN_ID: [{"version": "1.0.0", "installPath": "/cache/pstack/1.0.0"}]}
75
+ plugin_session_baseline.write_baseline_record("session", snapshot, [PLUGIN_ID])
76
+ assert plugin_session_baseline.read_baseline_record("session") == {
77
+ "plugins": snapshot,
78
+ "warned": [PLUGIN_ID],
79
+ }
80
+
81
+
82
+ def test_all_changed_plugin_ids_should_name_changed_and_new_plugins_only() -> None:
83
+ unchanged_entries = [{"version": "1.0.0", "installPath": "/cache/a/1.0.0"}]
84
+ baseline_snapshot = {
85
+ "a@m": unchanged_entries,
86
+ "b@m": [{"version": "1.0.0", "installPath": "/cache/b/1.0.0"}],
87
+ }
88
+ current_snapshot = {
89
+ "a@m": unchanged_entries,
90
+ "b@m": [{"version": "2.0.0", "installPath": "/cache/b/2.0.0"}],
91
+ "c@m": [{"version": "1.0.0", "installPath": "/cache/c/1.0.0"}],
92
+ }
93
+ assert plugin_session_baseline.all_changed_plugin_ids(
94
+ baseline_snapshot, current_snapshot
95
+ ) == [
96
+ "b@m",
97
+ "c@m",
98
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.96.7",
3
+ "version": "8.97.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -136,6 +136,7 @@ def _names_exempt_registration_path(registered_string: str) -> bool:
136
136
  hooks/blocking/verify_before_acting.py -> exempt
137
137
  hooks/blocking/session_title_format_gate.py -> exempt
138
138
  hooks/blocking/artifact_dark_mode_gate.py -> exempt
139
+ hooks/blocking/send_user_file_worktree_root_gate.py -> exempt
139
140
  hooks/blocking/build_eval_before_eval_write_gate.py -> exempt
140
141
  hooks/blocking/merge_over_review_offer_gate.py -> exempt
141
142
  hooks/blocking/final_reply_gate.py -> exempt
@@ -174,6 +175,10 @@ def _names_exempt_registration_path(registered_string: str) -> bool:
174
175
  The artifact page gate denies a page publish whose text is unreadable in
175
176
  dark mode. It decides how a page renders and no code or safety policy.
176
177
 
178
+ The send user file gate denies a SendUserFile path outside the session's
179
+ working directory, which the desktop file pane cannot open. It decides
180
+ where a sent file sits and no code or safety policy.
181
+
177
182
  The eval write gate denies a write to an eval file until the session
178
183
  invokes ``/claude-api build-eval``. It holds eval writes until a skill
179
184
  loads, and it judges no code or safety content.
@@ -74,6 +74,7 @@ ALL_ACTION_BOUNDARY_EXEMPT_REGISTRATION_PATHS = (
74
74
  "blocking/session_title_format_gate.py",
75
75
  "blocking/image_read_size_gate.py",
76
76
  "blocking/artifact_dark_mode_gate.py",
77
+ "blocking/send_user_file_worktree_root_gate.py",
77
78
  "blocking/haiku_spawn_slim_gate.py",
78
79
  "blocking/build_eval_before_eval_write_gate.py",
79
80
  "blocking/merge_over_review_offer_gate.py",
@@ -154,6 +154,14 @@ def test_new_artifact_page_gate_registration_is_clean(tmp_path: Path) -> None:
154
154
  assert adapters.hook_configuration_diagnostics(current_document, tmp_path) == ()
155
155
 
156
156
 
157
+ def test_new_send_user_file_gate_registration_is_clean(tmp_path: Path) -> None:
158
+ current_document = _hook_document(
159
+ ["hooks/blocking/send_user_file_worktree_root_gate.py"],
160
+ '{"hooks": {}}',
161
+ )
162
+ assert adapters.hook_configuration_diagnostics(current_document, tmp_path) == ()
163
+
164
+
157
165
  def test_new_eval_write_gate_registration_is_clean(tmp_path: Path) -> None:
158
166
  current_document = _hook_document(
159
167
  ["hooks/blocking/build_eval_before_eval_write_gate.py"],