claude-dev-env 8.75.4 → 8.75.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.75.4",
3
+ "version": "8.75.5",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -216,10 +216,20 @@ def _is_inside(candidate_text: str, directory: Path) -> bool:
216
216
  return _comparable_path(candidate_text).startswith(comparable_directory + os.sep)
217
217
 
218
218
 
219
+ def _link_target_is_present(link_path: Path) -> bool:
220
+ try:
221
+ link_path.stat()
222
+ except PermissionError:
223
+ return True
224
+ except OSError:
225
+ return False
226
+ return True
227
+
228
+
219
229
  def _unlink_orphaned_links(main_home: Path, profile_home: Path) -> tuple[str, ...]:
220
230
  all_unlinked: list[str] = []
221
231
  for each_entry in sorted(profile_home.iterdir()):
222
- if not _is_link(each_entry) or each_entry.exists():
232
+ if not _is_link(each_entry) or _link_target_is_present(each_entry):
223
233
  continue
224
234
  if not _is_inside(_link_target_text(each_entry), main_home):
225
235
  continue
@@ -184,6 +184,29 @@ class TestSyncProfile:
184
184
  assert not os.path.lexists(profile_home / "rules")
185
185
  assert report.all_unlinked == ("rules",)
186
186
 
187
+ def test_should_keep_a_link_whose_target_refuses_access(
188
+ self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
189
+ ) -> None:
190
+ main_home = build_main_home(tmp_path)
191
+ (main_home / ".pytest_cache").mkdir()
192
+ profile_home = tmp_path / "ev"
193
+ profile_home.mkdir()
194
+ locked_link = profile_home / ".pytest_cache"
195
+ locked_link.symlink_to(main_home / ".pytest_cache", target_is_directory=True)
196
+ unlocked_stat = Path.stat
197
+
198
+ def stat_refusing_the_locked_link(
199
+ self: Path, *, follow_symlinks: bool = True
200
+ ) -> os.stat_result:
201
+ if self == locked_link and follow_symlinks:
202
+ raise PermissionError(5, "Access is denied", str(self))
203
+ return unlocked_stat(self, follow_symlinks=follow_symlinks)
204
+
205
+ monkeypatch.setattr(Path, "stat", stat_refusing_the_locked_link)
206
+ report = sync(main_home, profile_home)
207
+ assert profile.links_to(locked_link, main_home / ".pytest_cache")
208
+ assert ".pytest_cache" not in report.all_unlinked
209
+
187
210
 
188
211
  def test_should_remove_a_main_link_left_under_an_account_local_name(
189
212
  self, tmp_path: Path