mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-14 22:17:35 +00:00
Bugfix: list_memories was not recursing into symlinked dirs.
This commit is contained in:
1 parent
b174a2cf72
commit
310a01c1ac
3 files changed
+57
-3
No files matched your search
+4
-1
@@ -72,7 +72,10 @@ Status of the `main` branch. Changes prior to the next official version change w
|
||||
- Tray manager: Add NixOS-support for AppIndicator-based trays (e.g., most Wayland-trays) to the package in flake.nix.
|
||||
- Fix: Wait for the subprocess that opens the browser window, preventing zombie processes #1488
|
||||
|
||||
Dependencies:
|
||||
* Memories:
|
||||
- Make memory iteration follow symbolic links
|
||||
|
||||
* Dependencies:
|
||||
- Add dependency `oslex`
|
||||
|
||||
# v1.5.3 (2026-05-26)
|
||||
|
||||
@@ -2,7 +2,8 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
from collections.abc import Sequence
|
||||
import sys
|
||||
from collections.abc import Iterator, Sequence
|
||||
from pathlib import Path
|
||||
from typing import Literal
|
||||
|
||||
@@ -233,11 +234,27 @@ class MemoryManager:
|
||||
def get_full_list(self) -> list[str]:
|
||||
return sorted(self.memories + self.read_only_memories)
|
||||
|
||||
@staticmethod
|
||||
def _iter_memory_files(search_dir: Path) -> Iterator[Path]:
|
||||
"""Yields every ``*.md`` file under ``search_dir``, descending into symlinked directories.
|
||||
|
||||
Directory symlinks must be followed so that memories shared via symlink are discovered
|
||||
(e.g. a monorepo whose ``.serena/memories`` folder symlinks each submodule's memory
|
||||
directory, making them addressable as ``<submodule>/<name>``). ``Path.rglob`` only
|
||||
follows symlinked directories from Python 3.13 onwards, via ``recurse_symlinks``; on
|
||||
3.11/3.12 it silently skips them, so we fall back to ``os.walk(followlinks=True)`` there.
|
||||
``rglob`` is preferred when available as it is faster than ``os.walk``.
|
||||
"""
|
||||
for root, _dirs, files in os.walk(search_dir, followlinks=True):
|
||||
for filename in files:
|
||||
if filename.endswith(".md"):
|
||||
yield Path(root) / filename
|
||||
|
||||
def _list_memories(self, search_dir: Path, base_dir: Path, prefix: str = "") -> MemoriesList:
|
||||
result = self.MemoriesList()
|
||||
if not search_dir.exists():
|
||||
return result
|
||||
for md_file in search_dir.rglob("*.md"):
|
||||
for md_file in self._iter_memory_files(search_dir):
|
||||
rel = str(md_file.relative_to(base_dir).with_suffix("")).replace(os.sep, "/")
|
||||
memory_name = prefix + rel
|
||||
if self._is_ignored_memory(memory_name):
|
||||
|
||||
@@ -269,6 +269,40 @@ def _write(manager: MemoryManager, name: str, content: str) -> None:
|
||||
manager.save_memory(name, content, is_tool_context=False)
|
||||
|
||||
|
||||
class TestListMemoriesFollowsSymlinks:
|
||||
"""Regression: memories reachable only through a directory symlink (e.g. a monorepo whose
|
||||
``.serena/memories`` symlinks each submodule's memory folder, making them addressable as
|
||||
``<submodule>/<name>``) must be listed and readable. ``Path.rglob`` does not descend into
|
||||
symlinked directories before Python 3.13, which silently hid them from ``list_memories``.
|
||||
"""
|
||||
|
||||
def test_lists_and_reads_memories_behind_directory_symlink(self, tmp_path) -> None:
|
||||
project_dir = tmp_path / "project" / ".serena"
|
||||
manager = MemoryManager(serena_data_folder=project_dir)
|
||||
_write(manager, "own_memory", "# local")
|
||||
|
||||
# a sibling submodule with its own (possibly nested) memories
|
||||
submodule_memories = tmp_path / "submodule" / ".serena" / "memories"
|
||||
(submodule_memories / "nested").mkdir(parents=True)
|
||||
(submodule_memories / "sub_a.md").write_text("# a", encoding="utf-8")
|
||||
(submodule_memories / "nested" / "sub_b.md").write_text("# b", encoding="utf-8")
|
||||
|
||||
# symlink the submodule's memory dir into the project's memories folder
|
||||
link = project_dir / "memories" / "submodule"
|
||||
try:
|
||||
link.symlink_to(submodule_memories, target_is_directory=True)
|
||||
except (OSError, NotImplementedError) as e:
|
||||
pytest.skip(f"cannot create directory symlinks on this platform/permissions: {e}")
|
||||
|
||||
listed = manager.list_memories().get_full_list()
|
||||
assert "own_memory" in listed
|
||||
assert "submodule/sub_a" in listed
|
||||
assert "submodule/nested/sub_b" in listed
|
||||
# the listed names must round-trip through the read path (which resolves the symlink)
|
||||
assert manager.load_memory("submodule/sub_a") == "# a"
|
||||
assert manager.load_memory("submodule/nested/sub_b") == "# b"
|
||||
|
||||
|
||||
class TestValidateReferentialIntegrity:
|
||||
def test_clean_report_when_all_references_resolve(self, fs_manager: MemoryManager) -> None:
|
||||
_write(fs_manager, "auth", "# auth notes")
|
||||
|
||||
Reference in new issue
Block a user