- 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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
71f6226f0d
commit
8a5fbf6152
@@ -43,6 +43,10 @@ llm = [
|
|||||||
trinity = [
|
trinity = [
|
||||||
"trinity-pattern>=1.0.0",
|
"trinity-pattern>=1.0.0",
|
||||||
]
|
]
|
||||||
|
memory = [
|
||||||
|
"numpy>=2.0",
|
||||||
|
"chromadb>=1.0",
|
||||||
|
]
|
||||||
seedgo = []
|
seedgo = []
|
||||||
dev = [
|
dev = [
|
||||||
"pytest",
|
"pytest",
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ fi
|
|||||||
echo "Upgrading pip ..."
|
echo "Upgrading pip ..."
|
||||||
"$VENV_PYTHON" -m pip install --upgrade pip --quiet
|
"$VENV_PYTHON" -m pip install --upgrade pip --quiet
|
||||||
|
|
||||||
echo "Installing aipass in editable mode (with dev extras) ..."
|
echo "Installing aipass in editable mode (with dev + memory extras) ..."
|
||||||
"$VENV_PYTHON" -m pip install -e ".[dev]" --quiet
|
"$VENV_PYTHON" -m pip install -e ".[dev,memory]" --quiet
|
||||||
|
|
||||||
# --- Detect shadowing drone installs (Windows) ---
|
# --- Detect shadowing drone installs (Windows) ---
|
||||||
# Issues #317 + #321: system-Python pip or legacy npm aipass-drone can shadow venv drone.exe.
|
# Issues #317 + #321: system-Python pip or legacy npm aipass-drone can shadow venv drone.exe.
|
||||||
|
|||||||
@@ -635,6 +635,8 @@ def poll_cycle(config: Dict[str, Any], state: Dict[str, Any]) -> int:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
branch_path = Path(branch_path_str)
|
branch_path = Path(branch_path_str)
|
||||||
|
if not branch_path.is_absolute():
|
||||||
|
branch_path = _REPO_ROOT / branch_path
|
||||||
|
|
||||||
if is_protected_branch(branch_email):
|
if is_protected_branch(branch_email):
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -316,8 +316,11 @@ def main():
|
|||||||
# even after cd'ing away. Drone reads this as fallback for caller detection.
|
# even after cd'ing away. Drone reads this as fallback for caller detection.
|
||||||
spawn_env["AIPASS_BRANCH_NAME"] = branch_email.lstrip("@")
|
spawn_env["AIPASS_BRANCH_NAME"] = branch_email.lstrip("@")
|
||||||
|
|
||||||
# Extract CWD from lock file path (branch_path/.ai_mail.local/.dispatch.lock)
|
# Extract CWD from lock file path (branch_path/.ai_mail.local/.dispatch.lock).
|
||||||
lock_path = Path(lock_file)
|
# 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
|
branch_path = lock_path.parent.parent
|
||||||
cwd = str(branch_path)
|
cwd = str(branch_path)
|
||||||
|
|
||||||
|
|||||||
@@ -565,6 +565,26 @@
|
|||||||
"standard": "unused_function",
|
"standard": "unused_function",
|
||||||
"lines": [427],
|
"lines": [427],
|
||||||
"reason": "LLM-based extraction function — called conditionally when API key is available."
|
"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": {
|
"notes": {
|
||||||
|
|||||||
@@ -125,6 +125,41 @@ def _get_rollover_threshold(branch_name: str, file_path: Path | None = None) ->
|
|||||||
return 600
|
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]:
|
def check_and_rollover() -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Check all memory files and trigger rollover if any exceed their threshold.
|
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
|
_startup_check_done = True
|
||||||
|
|
||||||
|
vector_deps_ok = _check_vector_deps()
|
||||||
|
|
||||||
results = {
|
results = {
|
||||||
"success": True,
|
"success": True,
|
||||||
"files_checked": 0,
|
"files_checked": 0,
|
||||||
"files_over_limit": [],
|
"files_over_limit": [],
|
||||||
"rollover_triggered": False,
|
"rollover_triggered": False,
|
||||||
"memory_pool": None,
|
"memory_pool": None,
|
||||||
|
"vector_storage": "healthy" if vector_deps_ok else "degraded — chromadb/numpy not installed",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get all branch paths
|
# Get all branch paths
|
||||||
|
|||||||
@@ -22,7 +22,10 @@ import sys
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
import numpy as np
|
import pytest
|
||||||
|
|
||||||
|
np = pytest.importorskip("numpy")
|
||||||
|
pytest.importorskip("chromadb")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user