From 8a5fbf6152d8b6ba9667889133574764f4cf5c0a Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Mon, 20 Apr 2026 13:06:20 -0700 Subject: [PATCH] =?UTF-8?q?feat(memory):=20add=20[memory]=20extras=20group?= =?UTF-8?q?=20=E2=80=94=20unblock=20contributor=20pytest=20(#360=20finding?= =?UTF-8?q?=20#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pyproject.toml: add memory = ["numpy>=2.0", "chromadb>=1.0"] optional-deps group - setup.sh: install .[dev,memory] so fresh-clone pytest works end-to-end - test_vector.py: gate with pytest.importorskip("numpy"/"chromadb") — skip cleanly without extras - memory_watcher.py: _check_vector_deps() probes venv at startup; health report now honest when chromadb absent - bypass.json: 4 entries covering test_vector.py seedgo false-positives (architecture/docs/encapsulation/meta) - dispatch/daemon.py: resolve relative branch_path to absolute before use - dispatch/dispatch_monitor.py: resolve lock_file path so claude cwd is always absolute Co-Authored-By: Claude Sonnet 4.6 --- pyproject.toml | 4 ++ setup.sh | 4 +- .../ai_mail/apps/handlers/dispatch/daemon.py | 2 + .../handlers/dispatch/dispatch_monitor.py | 7 +++- src/aipass/memory/.seedgo/bypass.json | 20 ++++++++++ .../apps/handlers/monitor/memory_watcher.py | 38 +++++++++++++++++++ src/aipass/memory/tests/test_vector.py | 5 ++- 7 files changed, 75 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 74fdae83..1c1e8ea9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,10 @@ llm = [ trinity = [ "trinity-pattern>=1.0.0", ] +memory = [ + "numpy>=2.0", + "chromadb>=1.0", +] seedgo = [] dev = [ "pytest", diff --git a/setup.sh b/setup.sh index 1f81f4c5..878197d0 100755 --- a/setup.sh +++ b/setup.sh @@ -177,8 +177,8 @@ fi echo "Upgrading pip ..." "$VENV_PYTHON" -m pip install --upgrade pip --quiet -echo "Installing aipass in editable mode (with dev extras) ..." -"$VENV_PYTHON" -m pip install -e ".[dev]" --quiet +echo "Installing aipass in editable mode (with dev + memory extras) ..." +"$VENV_PYTHON" -m pip install -e ".[dev,memory]" --quiet # --- Detect shadowing drone installs (Windows) --- # Issues #317 + #321: system-Python pip or legacy npm aipass-drone can shadow venv drone.exe. diff --git a/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py b/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py index fe8b594a..7e26cbdd 100644 --- a/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py +++ b/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py @@ -635,6 +635,8 @@ def poll_cycle(config: Dict[str, Any], state: Dict[str, Any]) -> int: continue branch_path = Path(branch_path_str) + if not branch_path.is_absolute(): + branch_path = _REPO_ROOT / branch_path if is_protected_branch(branch_email): continue diff --git a/src/aipass/ai_mail/apps/handlers/dispatch/dispatch_monitor.py b/src/aipass/ai_mail/apps/handlers/dispatch/dispatch_monitor.py index e6516cb1..ae0fab1d 100644 --- a/src/aipass/ai_mail/apps/handlers/dispatch/dispatch_monitor.py +++ b/src/aipass/ai_mail/apps/handlers/dispatch/dispatch_monitor.py @@ -316,8 +316,11 @@ def main(): # even after cd'ing away. Drone reads this as fallback for caller detection. spawn_env["AIPASS_BRANCH_NAME"] = branch_email.lstrip("@") - # Extract CWD from lock file path (branch_path/.ai_mail.local/.dispatch.lock) - lock_path = Path(lock_file) + # Extract CWD from lock file path (branch_path/.ai_mail.local/.dispatch.lock). + # Resolve to absolute so the cwd passed to claude is never relative — a relative + # cwd would be interpreted against dispatch_monitor's own cwd and produce the + # wrong directory for branches like @ai_mail whose registry path is relative. + lock_path = Path(lock_file).resolve() branch_path = lock_path.parent.parent cwd = str(branch_path) diff --git a/src/aipass/memory/.seedgo/bypass.json b/src/aipass/memory/.seedgo/bypass.json index 1c963679..0d8b4a5e 100644 --- a/src/aipass/memory/.seedgo/bypass.json +++ b/src/aipass/memory/.seedgo/bypass.json @@ -565,6 +565,26 @@ "standard": "unused_function", "lines": [427], "reason": "LLM-based extraction function — called conditionally when API key is available." + }, + { + "file": "tests/test_vector.py", + "standard": "architecture", + "reason": "Test file — lives in tests/ by design, not in 3-layer apps/ structure." + }, + { + "file": "tests/test_vector.py", + "standard": "documentation", + "reason": "Test file — test functions don't require docstrings." + }, + { + "file": "tests/test_vector.py", + "standard": "encapsulation", + "reason": "Test file — direct handler imports are correct for unit testing handler internals." + }, + { + "file": "tests/test_vector.py", + "standard": "meta", + "reason": "Test file — META block present at lines 1-7; hook false-positive on test file format." } ], "notes": { diff --git a/src/aipass/memory/apps/handlers/monitor/memory_watcher.py b/src/aipass/memory/apps/handlers/monitor/memory_watcher.py index 542ef974..b84174b5 100644 --- a/src/aipass/memory/apps/handlers/monitor/memory_watcher.py +++ b/src/aipass/memory/apps/handlers/monitor/memory_watcher.py @@ -125,6 +125,41 @@ def _get_rollover_threshold(branch_name: str, file_path: Path | None = None) -> return 600 +def _check_vector_deps() -> bool: + """ + Check whether the memory venv has chromadb and numpy available. + + Runs a quick subprocess check using the memory venv Python. + Logs a warning if deps are missing so the self-report is honest. + + Returns: + True if both chromadb and numpy are importable, False otherwise + """ + import subprocess + import sys + + venv_python = _MEMORY_ROOT / ".venv" / "bin" / "python3" + if not venv_python.exists(): + venv_python = Path(sys.executable) + + try: + result = subprocess.run( + [str(venv_python), "-c", "import chromadb; import numpy"], + capture_output=True, + timeout=10, + ) + if result.returncode != 0: + logger.warning( + "[memory_watcher] vector storage degraded — chromadb/numpy not available in memory venv. " + "Run: pip install -e '.[dev,memory]'" + ) + return False + return True + except Exception as e: + logger.warning(f"[memory_watcher] vector dep check failed: {e}") + return False + + def check_and_rollover() -> Dict[str, Any]: """ Check all memory files and trigger rollover if any exceed their threshold. @@ -146,12 +181,15 @@ def check_and_rollover() -> Dict[str, Any]: _startup_check_done = True + vector_deps_ok = _check_vector_deps() + results = { "success": True, "files_checked": 0, "files_over_limit": [], "rollover_triggered": False, "memory_pool": None, + "vector_storage": "healthy" if vector_deps_ok else "degraded — chromadb/numpy not installed", } # Get all branch paths diff --git a/src/aipass/memory/tests/test_vector.py b/src/aipass/memory/tests/test_vector.py index 06c631cb..38a48cb7 100644 --- a/src/aipass/memory/tests/test_vector.py +++ b/src/aipass/memory/tests/test_vector.py @@ -22,7 +22,10 @@ import sys from typing import Any from unittest.mock import MagicMock -import numpy as np +import pytest + +np = pytest.importorskip("numpy") +pytest.importorskip("chromadb") # ---------------------------------------------------------------------------