feat(memory): fix silent catch violations — add logger calls to all except blocks
Co-Authored-By: @memory <memory@aipass>
This commit is contained in:
@@ -52,6 +52,7 @@ def extract_file_info(file_path: Path) -> Dict[str, Any]:
|
||||
try:
|
||||
tree = ast.parse(content)
|
||||
except SyntaxError:
|
||||
logger.info(f"[indexer] Syntax error parsing {file_path.name}, skipping AST extraction")
|
||||
return {
|
||||
'filename': file_path.name,
|
||||
'path': str(file_path.relative_to(CODE_ARCHIVE_PATH)),
|
||||
@@ -85,6 +86,7 @@ def extract_file_info(file_path: Path) -> Dict[str, Any]:
|
||||
'indexed_at': datetime.now().isoformat()
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"[indexer] Failed to extract file info from {file_path.name}: {e}")
|
||||
return {
|
||||
'filename': file_path.name,
|
||||
'path': str(file_path),
|
||||
@@ -120,8 +122,8 @@ def load_index() -> Dict[str, Any]:
|
||||
try:
|
||||
with open(INDEX_PATH) as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[indexer] Failed to load index file: {e}")
|
||||
|
||||
return {
|
||||
'metadata': {
|
||||
@@ -155,6 +157,7 @@ def save_index(index: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
return {'success': True}
|
||||
except Exception as e:
|
||||
logger.error(f"[indexer] Failed to save index: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,8 @@ def count_chroma_vectors() -> int:
|
||||
|
||||
return total
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[central_writer] Failed to count chroma vectors: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -112,6 +113,7 @@ def count_archive_files() -> int:
|
||||
return len(archive_files)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[central_writer] Failed to count archive files: {e}")
|
||||
raise Exception(f"Failed to count archive files: {e}")
|
||||
|
||||
|
||||
@@ -141,6 +143,7 @@ def get_last_rollover_timestamp() -> str:
|
||||
return mtime.isoformat()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[central_writer] Failed to get rollover timestamp: {e}")
|
||||
raise Exception(f"Failed to get rollover timestamp: {e}")
|
||||
|
||||
|
||||
@@ -195,6 +198,7 @@ def read_central_file() -> Dict[str, Any]:
|
||||
return json_load(f)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[central_writer] Failed to read central file: {e}")
|
||||
raise Exception(f"Failed to read central file: {e}")
|
||||
|
||||
|
||||
@@ -216,6 +220,7 @@ def write_central_file(data: Dict[str, Any]) -> None:
|
||||
json_dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[central_writer] Failed to write central file: {e}")
|
||||
raise Exception(f"Failed to write central file: {e}")
|
||||
|
||||
|
||||
@@ -276,6 +281,7 @@ def update_central(verbose: bool = False) -> Dict[str, Any]:
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[central_writer] Failed to update central: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
@@ -303,6 +309,7 @@ def get_current_stats() -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[central_writer] Failed to get current stats: {e}")
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
|
||||
@@ -80,7 +80,8 @@ def _read_central_stats() -> Dict[str, Any]:
|
||||
"total_archives": stats.get("total_archives", 0),
|
||||
"last_rollover": stats.get("last_rollover", "")
|
||||
}
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to read central stats: {e}")
|
||||
return {"total_vectors": 0, "total_archives": 0, "last_rollover": ""}
|
||||
|
||||
|
||||
@@ -104,7 +105,8 @@ def _get_collections_count() -> int:
|
||||
count = cursor.fetchone()[0]
|
||||
conn.close()
|
||||
return count
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to count collections: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -125,7 +127,8 @@ def _get_rollover_config() -> Dict[str, Any]:
|
||||
"defaults": rollover.get("defaults", {"max_lines": 600, "buffer": 100}),
|
||||
"per_branch": rollover.get("per_branch", {})
|
||||
}
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to load rollover config: {e}")
|
||||
return {"defaults": {"max_lines": 600, "buffer": 100}, "per_branch": {}}
|
||||
|
||||
|
||||
@@ -237,11 +240,13 @@ def _find_branches_near_rollover() -> List[Dict[str, Any]]:
|
||||
"current_lines": current_lines,
|
||||
"max_lines": max_lines
|
||||
})
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# Skip files that can't be read
|
||||
logger.warning(f"[dashboard_push] Failed to read memory file {memory_file}: {e}")
|
||||
continue
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to scan branches for rollover: {e}")
|
||||
return near_rollover # Return partial results on registry read failure
|
||||
|
||||
# Sort by lines_remaining ascending (most urgent first)
|
||||
@@ -262,7 +267,8 @@ def _get_template_version() -> str:
|
||||
|
||||
data = json_loads(TEMPLATE_VERSION_FILE.read_text(encoding="utf-8"))
|
||||
return data.get("version", "unknown")
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to read template version: {e}")
|
||||
return "unknown"
|
||||
|
||||
|
||||
@@ -283,6 +289,7 @@ def _get_last_rollover_info(central_stats: Dict) -> Dict[str, str]:
|
||||
dt = datetime.fromisoformat(last_rollover_ts)
|
||||
return {"date": dt.strftime("%Y-%m-%d")}
|
||||
except (ValueError, TypeError):
|
||||
logger.info(f"[dashboard_push] Could not parse rollover timestamp: {last_rollover_ts}")
|
||||
return {"date": last_rollover_ts}
|
||||
return {"date": "never"}
|
||||
|
||||
@@ -309,7 +316,8 @@ def _get_all_branch_paths() -> List[Path]:
|
||||
if branch_path.exists():
|
||||
paths.append(branch_path)
|
||||
return paths
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to get branch paths: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -401,7 +409,8 @@ def _write_section_to_all_branches(section_name: str, section_data: Dict,
|
||||
if result.returncode == 0 and result.stdout.strip().isdigit():
|
||||
return int(result.stdout.strip())
|
||||
return 0
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[dashboard_push] Failed to write section to branches: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -435,7 +444,8 @@ def push_memory_bank_dashboard() -> bool:
|
||||
|
||||
return success_count > 0
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.error(f"[dashboard_push] Failed to push dashboard: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -147,7 +147,8 @@ def _load_manifest() -> Dict[str, str]:
|
||||
if _PROCESSED_MANIFEST.exists():
|
||||
try:
|
||||
return json.loads(_PROCESSED_MANIFEST.read_text(encoding='utf-8'))
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[plans_processor] Failed to load processed manifest: {e}")
|
||||
return {}
|
||||
return {}
|
||||
|
||||
@@ -175,6 +176,7 @@ def _embed_texts(texts: List[str]) -> dict:
|
||||
return {'success': False, 'error': result.stderr or 'Embedding failed'}
|
||||
return json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
logger.warning(f"[plans_processor] Embedding subprocess failed: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -199,6 +201,7 @@ def _store_vectors(embeddings, documents, metadatas, collection_name="flow_plans
|
||||
return {'success': False, 'error': result.stderr or 'Storage failed'}
|
||||
return json.loads(result.stdout)
|
||||
except Exception as e:
|
||||
logger.warning(f"[plans_processor] Vector storage subprocess failed: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -227,6 +230,7 @@ def process_plans() -> Dict[str, Any]:
|
||||
config = json.loads(config_path.read_text(encoding='utf-8'))
|
||||
plans_config = config.get('plans', {})
|
||||
except Exception as e:
|
||||
logger.warning(f"[plans_processor] Config load failed: {e}")
|
||||
return {'success': False, 'error': f'Config load failed: {e}'}
|
||||
|
||||
if not plans_config.get('enabled', False):
|
||||
@@ -273,6 +277,7 @@ def process_plans() -> Dict[str, Any]:
|
||||
try:
|
||||
text = plan_file.read_text(encoding='utf-8')
|
||||
except Exception as e:
|
||||
logger.warning(f"[plans_processor] Failed to read plan file {plan_file.name}: {e}")
|
||||
errors.append(f'{plan_file.name}: read error: {e}')
|
||||
continue
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# Paths
|
||||
@@ -47,8 +48,8 @@ def _notify_failure(subject: str, message: str) -> None:
|
||||
timeout=30,
|
||||
cwd=str(_MEMORY_ROOT)
|
||||
)
|
||||
except Exception:
|
||||
pass # Best-effort - don't let notification failure break processing
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Failed to send failure notification: {e}")
|
||||
|
||||
|
||||
def _update_central_and_dashboard() -> None:
|
||||
@@ -68,8 +69,8 @@ def _update_central_and_dashboard() -> None:
|
||||
"update_central()"],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
except Exception:
|
||||
pass # Non-critical: central update failure does not break pool processing
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Central stats update failed: {e}")
|
||||
|
||||
# Push dashboard to all branches
|
||||
try:
|
||||
@@ -79,8 +80,8 @@ def _update_central_and_dashboard() -> None:
|
||||
"push_memory_bank_dashboard()"],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
except Exception:
|
||||
pass # Non-critical: dashboard push failure does not break pool processing
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Dashboard push failed: {e}")
|
||||
|
||||
|
||||
def find_source_file(filename: str) -> Path | None:
|
||||
@@ -115,6 +116,7 @@ def load_config() -> dict:
|
||||
config = json.load(f)
|
||||
return config.get('memory_pool', {})
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Failed to load config: {e}")
|
||||
return {'enabled': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -166,6 +168,7 @@ def read_file_content(file_path: Path) -> dict:
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Failed to read file content: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -286,6 +289,7 @@ def process_file_to_vectors(file_path: Path, collection_name: str, chunk_size: i
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Failed to process file to vectors: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -335,6 +339,7 @@ def archive_old_files(keep_recent: int, archive_path: str = 'memory_pool_archive
|
||||
shutil.move(str(file_path), str(dest))
|
||||
archived_count += 1
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Failed to archive {file_path.name}: {e}")
|
||||
errors.append(f"{file_path.name}: {e}")
|
||||
|
||||
result = {
|
||||
@@ -464,8 +469,8 @@ def get_pool_status() -> dict:
|
||||
if collection_name in [c.name for c in client.list_collections()]:
|
||||
collection = client.get_collection(name=collection_name)
|
||||
collection_count = collection.count()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[pool_processor] Failed to get collection count: {e}")
|
||||
|
||||
return {
|
||||
'enabled': config.get('enabled', False),
|
||||
|
||||
@@ -17,6 +17,8 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
_BRANCH_ROOT = Path(__file__).resolve().parents[3]
|
||||
_BRANCH_NAME = _BRANCH_ROOT.name
|
||||
JSON_DIR = _BRANCH_ROOT / f"{_BRANCH_NAME}_json"
|
||||
@@ -27,6 +29,7 @@ def read_json(file_path: Path) -> dict | None:
|
||||
try:
|
||||
return json.loads(file_path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
logger.info(f"[json_handler] Could not read JSON from {file_path}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -36,7 +39,8 @@ def write_json(file_path: Path, data: Any, indent: int = 2) -> bool:
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
file_path.write_text(json.dumps(data, indent=indent) + "\n", encoding="utf-8")
|
||||
return True
|
||||
except OSError:
|
||||
except OSError as e:
|
||||
logger.warning(f"[json_handler] Failed to write JSON to {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -75,6 +79,7 @@ def log_operation(operation: str, data: Dict[str, Any] | None = None, module_nam
|
||||
try:
|
||||
log = json.loads(log_path.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError):
|
||||
logger.info(f"[json_handler] Could not read operation log {log_path}, starting fresh")
|
||||
log = []
|
||||
|
||||
entry: Dict[str, Any] = {
|
||||
@@ -93,5 +98,6 @@ def log_operation(operation: str, data: Dict[str, Any] | None = None, module_nam
|
||||
try:
|
||||
log_path.write_text(json.dumps(log, indent=2) + "\n", encoding="utf-8")
|
||||
return True
|
||||
except OSError:
|
||||
except OSError as e:
|
||||
logger.warning(f"[json_handler] Failed to write operation log to {log_path}: {e}")
|
||||
return False
|
||||
|
||||
@@ -85,18 +85,21 @@ def read_memory_file(file_path: Path) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[memory_files] Corrupt JSON in {file_path.name}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Corrupt JSON in {file_path.name}: {e}"
|
||||
}
|
||||
|
||||
except PermissionError:
|
||||
logger.warning(f"[memory_files] Permission denied reading {file_path.name}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Permission denied reading {file_path.name}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_files] Failed to read {file_path.name}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to read {file_path.name}: {e}"
|
||||
@@ -161,16 +164,19 @@ def write_memory_file(file_path: Path, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
|
||||
except Exception as e:
|
||||
# Clean up temp file on failure
|
||||
logger.error(f"[memory_files] Write failed for {file_path.name}, cleaning up temp file: {e}")
|
||||
Path(temp_path).unlink(missing_ok=True)
|
||||
raise e
|
||||
|
||||
except PermissionError:
|
||||
logger.warning(f"[memory_files] Permission denied writing {file_path.name}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Permission denied writing {file_path.name}"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_files] Failed to write {file_path.name}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to write {file_path.name}: {e}"
|
||||
|
||||
@@ -119,7 +119,8 @@ def get_entry_age(value: str) -> int:
|
||||
entry_date = datetime.strptime(timestamp, "%Y-%m-%d")
|
||||
age = (datetime.now() - entry_date).days
|
||||
return max(0, age)
|
||||
except ValueError:
|
||||
except ValueError as e:
|
||||
logger.warning(f"[learnings_manager] Failed to parse entry timestamp: {e}")
|
||||
return 999999
|
||||
|
||||
|
||||
@@ -346,6 +347,7 @@ def _vectorize_learnings(
|
||||
return {'success': False, 'error': 'No embeddings generated'}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Embedding error for key_learnings: {e}")
|
||||
return {'success': False, 'error': f"Embedding error: {e}"}
|
||||
|
||||
# Store in ChromaDB via subprocess
|
||||
@@ -379,10 +381,13 @@ def _vectorize_learnings(
|
||||
return json.loads(result.stdout)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[learnings_manager] Key learnings vectorization timed out")
|
||||
return {'success': False, 'error': 'Vectorization timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[learnings_manager] Invalid JSON from key_learnings vectorization: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON response: {e}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Key learnings vectorization error: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -433,6 +438,7 @@ def _vectorize_completed_tasks(
|
||||
return {'success': False, 'error': 'No embeddings generated'}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Embedding error for recently_completed: {e}")
|
||||
return {'success': False, 'error': f"Embedding error: {e}"}
|
||||
|
||||
# Store in ChromaDB via subprocess
|
||||
@@ -466,10 +472,13 @@ def _vectorize_completed_tasks(
|
||||
return json.loads(result.stdout)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[learnings_manager] Completed tasks vectorization timed out")
|
||||
return {'success': False, 'error': 'Vectorization timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[learnings_manager] Invalid JSON from completed tasks vectorization: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON response: {e}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Completed tasks vectorization error: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -497,6 +506,7 @@ def ensure_timestamps(file_path: Path) -> Dict[str, Any]:
|
||||
if data is None:
|
||||
return {'success': False, 'error': f'Failed to parse file: {file_path.name}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read file: {e}")
|
||||
return {'success': False, 'error': f'Failed to read file: {e}'}
|
||||
|
||||
learnings = _get_learnings(data)
|
||||
@@ -517,6 +527,7 @@ def ensure_timestamps(file_path: Path) -> Dict[str, Any]:
|
||||
try:
|
||||
write_memory_file_simple(file_path, data)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to write file: {e}")
|
||||
return {'success': False, 'error': f'Failed to write file: {e}'}
|
||||
|
||||
return {
|
||||
@@ -549,6 +560,7 @@ def enforce_limit(file_path: Path) -> Dict[str, Any]:
|
||||
if data is None:
|
||||
return {'success': False, 'error': f'Failed to parse file: {file_path.name}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read file: {e}")
|
||||
return {'success': False, 'error': f'Failed to read file: {e}'}
|
||||
|
||||
learnings = _get_learnings(data)
|
||||
@@ -595,6 +607,7 @@ def enforce_limit(file_path: Path) -> Dict[str, Any]:
|
||||
try:
|
||||
write_memory_file_simple(file_path, data)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to write file: {e}")
|
||||
return {'success': False, 'error': f'Failed to write file: {e}'}
|
||||
|
||||
json_handler.log_operation("enforce_limit", {"removed": to_remove_count, "remaining": len(to_keep), "success": True})
|
||||
@@ -633,6 +646,7 @@ def ensure_timestamps_completed(file_path: Path) -> Dict[str, Any]:
|
||||
if data is None:
|
||||
return {'success': False, 'error': f'Failed to parse file: {file_path.name}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read file: {e}")
|
||||
return {'success': False, 'error': f'Failed to read file: {e}'}
|
||||
|
||||
completed = _get_recently_completed(data)
|
||||
@@ -656,6 +670,7 @@ def ensure_timestamps_completed(file_path: Path) -> Dict[str, Any]:
|
||||
try:
|
||||
write_memory_file_simple(file_path, data)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to write file: {e}")
|
||||
return {'success': False, 'error': f'Failed to write file: {e}'}
|
||||
|
||||
return {
|
||||
@@ -688,6 +703,7 @@ def enforce_limit_completed(file_path: Path) -> Dict[str, Any]:
|
||||
if data is None:
|
||||
return {'success': False, 'error': f'Failed to parse file: {file_path.name}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read file: {e}")
|
||||
return {'success': False, 'error': f'Failed to read file: {e}'}
|
||||
|
||||
completed = _get_recently_completed(data)
|
||||
@@ -732,6 +748,7 @@ def enforce_limit_completed(file_path: Path) -> Dict[str, Any]:
|
||||
try:
|
||||
write_memory_file_simple(file_path, data)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to write file: {e}")
|
||||
return {'success': False, 'error': f'Failed to write file: {e}'}
|
||||
|
||||
return {
|
||||
@@ -770,6 +787,7 @@ def add_learning(
|
||||
if data is None:
|
||||
return {'success': False, 'error': f'Failed to parse file: {file_path.name}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read file: {e}")
|
||||
return {'success': False, 'error': f'Failed to read file: {e}'}
|
||||
|
||||
# Get existing learnings or create empty dict
|
||||
@@ -786,6 +804,7 @@ def add_learning(
|
||||
try:
|
||||
write_memory_file_simple(file_path, data)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to write file: {e}")
|
||||
return {'success': False, 'error': f'Failed to write file: {e}'}
|
||||
|
||||
# Enforce limit after adding
|
||||
@@ -826,6 +845,7 @@ def update_status_counts(file_path: Path) -> Dict[str, Any]:
|
||||
if data is None:
|
||||
return {'success': False, 'error': f'Failed to parse file: {file_path.name}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read file: {e}")
|
||||
return {'success': False, 'error': f'Failed to read file: {e}'}
|
||||
|
||||
# Get actual counts
|
||||
@@ -854,6 +874,7 @@ def update_status_counts(file_path: Path) -> Dict[str, Any]:
|
||||
try:
|
||||
write_memory_file_simple(file_path, data)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to write file: {e}")
|
||||
return {'success': False, 'error': f'Failed to write file: {e}'}
|
||||
|
||||
return {
|
||||
@@ -961,6 +982,7 @@ def process_all_branches() -> Dict[str, Any]:
|
||||
with open(registry_path, 'r', encoding='utf-8') as f:
|
||||
registry = json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"[learnings_manager] Failed to read registry: {e}")
|
||||
return {'success': False, 'error': f'Failed to read registry: {e}'}
|
||||
|
||||
branches = registry.get('branches', [])
|
||||
|
||||
@@ -100,7 +100,8 @@ def _read_registry() -> List[Dict[str, Any]]:
|
||||
branch['path'] = str(resolved)
|
||||
|
||||
return branches
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[detector] Failed to read registry: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -148,7 +149,8 @@ def _load_config() -> Dict[str, Any]:
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[detector] Failed to load config: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
@@ -169,8 +171,8 @@ def _count_file_lines(file_path: Path) -> int:
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return len(f.readlines())
|
||||
except Exception:
|
||||
# Silent failure - handlers don't log (3-tier architecture)
|
||||
except Exception as e:
|
||||
logger.warning(f"[detector] Failed to count lines in {file_path}: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -194,8 +196,8 @@ def _get_max_lines(file_path: Path, branch_name: str | None = None) -> int:
|
||||
file_limit = limits.get('max_lines')
|
||||
if file_limit is not None:
|
||||
return file_limit
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[detector] Failed to read file-level max_lines from {file_path}: {e}")
|
||||
|
||||
# 2. Try branch-level config (if branch_name provided or can be extracted)
|
||||
if branch_name is None:
|
||||
@@ -240,8 +242,9 @@ def _should_rollover(file_path: Path) -> tuple[bool, int, int, str, str]:
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
# Can't parse — fall back to line-based with hardcoded default
|
||||
logger.warning(f"[detector] Failed to parse {file_path} for rollover check: {e}")
|
||||
return (current_lines >= 600, current_lines, 600, '1.0.0', '')
|
||||
|
||||
metadata = data.get('document_metadata', {})
|
||||
|
||||
@@ -30,6 +30,9 @@ if TYPE_CHECKING:
|
||||
from watchdog.observers import Observer as _ObserverType
|
||||
from watchdog.events import FileSystemEventHandler as _HandlerType
|
||||
|
||||
# Temporary logger for module-level import guards (overwritten below by get_system_logger)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
@@ -38,6 +41,7 @@ except ImportError:
|
||||
WATCHDOG_AVAILABLE = False
|
||||
Observer = None
|
||||
FileSystemEventHandler = object # type: ignore[assignment,misc]
|
||||
logger.info("Optional dependency 'watchdog' not available")
|
||||
|
||||
# Handler imports (relative within package)
|
||||
from aipass.memory.apps.handlers.tracking.line_counter import update_line_count
|
||||
@@ -95,8 +99,8 @@ def _get_rollover_threshold(branch_name: str, file_path: Path | None = None) ->
|
||||
file_limit = metadata.get('limits', {}).get('max_lines')
|
||||
if file_limit is not None:
|
||||
return file_limit
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Failed to read file-level threshold from {file_path}: {e}")
|
||||
|
||||
# 2. Check per-branch config override
|
||||
config_path = _MEMORY_ROOT / "config" / "memory_bank.config.json"
|
||||
@@ -114,8 +118,8 @@ def _get_rollover_threshold(branch_name: str, file_path: Path | None = None) ->
|
||||
if default_limit is not None:
|
||||
return default_limit
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Failed to read rollover config: {e}")
|
||||
|
||||
# 4. Final fallback
|
||||
return 600
|
||||
@@ -191,8 +195,8 @@ def check_and_rollover() -> Dict[str, Any]:
|
||||
lines_synced += 1
|
||||
# Re-read actual line count after metadata update
|
||||
line_count = len(memory_file.read_text(encoding='utf-8').splitlines())
|
||||
except Exception:
|
||||
pass # Non-critical - sync is best-effort
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Non-critical metadata sync failed for {memory_file}: {e}")
|
||||
|
||||
if line_count > threshold:
|
||||
results['files_over_limit'].append({
|
||||
@@ -200,8 +204,8 @@ def check_and_rollover() -> Dict[str, Any]:
|
||||
'lines': line_count,
|
||||
'threshold': threshold
|
||||
})
|
||||
except Exception:
|
||||
pass # Skip files we can't read
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Failed to read memory file {memory_file}: {e}")
|
||||
|
||||
results['lines_synced'] = lines_synced
|
||||
|
||||
@@ -215,6 +219,7 @@ def check_and_rollover() -> Dict[str, Any]:
|
||||
except ImportError:
|
||||
logger.warning("Rollover handler not available")
|
||||
except Exception as e:
|
||||
logger.error(f"[memory_watcher] Rollover execution failed: {e}")
|
||||
results['rollover_error'] = str(e)
|
||||
results['success'] = False
|
||||
|
||||
@@ -252,7 +257,8 @@ def _check_memory_pool() -> Dict[str, Any]:
|
||||
with open(config_path) as f:
|
||||
config = json.load(f)
|
||||
pool_config = config.get('memory_pool', {})
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.warning(f"[memory_watcher] Could not load memory pool config: {exc}")
|
||||
return {'success': False, 'error': 'Could not load config'}
|
||||
|
||||
# Check if enabled
|
||||
@@ -290,6 +296,7 @@ def _check_memory_pool() -> Dict[str, Any]:
|
||||
'action': 'processed'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Memory pool processing failed: {e}")
|
||||
return {'success': False, 'error': str(e), 'action': 'failed'}
|
||||
|
||||
|
||||
@@ -313,7 +320,8 @@ def _check_plans() -> Dict[str, Any]:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
plans_config = config.get('plans', {})
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
logger.warning(f"[memory_watcher] Could not load plans config: {exc}")
|
||||
return {'success': False, 'error': 'Could not load config'}
|
||||
|
||||
# Check if enabled
|
||||
@@ -344,8 +352,8 @@ def _check_plans() -> Dict[str, Any]:
|
||||
if manifest_path.exists():
|
||||
try:
|
||||
manifest = json.loads(manifest_path.read_text(encoding='utf-8'))
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Failed to read plans manifest: {e}")
|
||||
|
||||
pending = [f for f in files if f.name not in manifest]
|
||||
pending_count = len(pending)
|
||||
@@ -367,6 +375,7 @@ def _check_code_archive() -> Dict[str, Any]:
|
||||
from aipass.memory.apps.handlers.archive.indexer import check_for_new_files
|
||||
return check_for_new_files()
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Code archive check failed: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -415,7 +424,8 @@ def _get_branch_paths() -> list[Path]:
|
||||
paths.append(branch_path)
|
||||
|
||||
return paths
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Failed to read branch paths from registry: {e}")
|
||||
return []
|
||||
|
||||
|
||||
@@ -546,8 +556,8 @@ def start_memory_watcher() -> Dict[str, Any]:
|
||||
try:
|
||||
new_observer.schedule(watcher, str(branch_path), recursive=False)
|
||||
watched_paths.append(str(branch_path))
|
||||
except Exception:
|
||||
pass # Skip invalid paths silently
|
||||
except Exception as e:
|
||||
logger.warning(f"[memory_watcher] Failed to schedule watcher for {branch_path}: {e}")
|
||||
|
||||
# Start observer
|
||||
new_observer.start()
|
||||
@@ -652,6 +662,7 @@ if __name__ == "__main__":
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
logger.info("[memory_watcher] Watcher stopped by user (KeyboardInterrupt)")
|
||||
print("\nStopping...")
|
||||
stop_memory_watcher()
|
||||
else:
|
||||
|
||||
@@ -78,6 +78,7 @@ def create_rollover_backup(file_path: Path) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[extractor] Backup failed for {file_path}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Backup failed: {e}'
|
||||
@@ -116,6 +117,7 @@ def restore_from_backup(file_path: Path) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[extractor] Restore from backup failed for {file_path}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Restore failed: {e}'
|
||||
@@ -328,6 +330,7 @@ def _extract_items_v2(file_path: Path, data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
_write_memory_file(file_path, data)
|
||||
new_lines = _count_file_lines(file_path)
|
||||
except Exception as e:
|
||||
logger.error(f"[extractor] Failed to write file after v2 extraction: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to write file: {e}"
|
||||
@@ -388,6 +391,7 @@ def extract_items(
|
||||
}
|
||||
current_lines = _count_file_lines(file_path)
|
||||
except Exception as e:
|
||||
logger.warning(f"[extractor] Failed to read file {file_path}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to read file: {e}"
|
||||
@@ -445,6 +449,7 @@ def extract_items(
|
||||
_write_memory_file(file_path, data)
|
||||
new_line_count = _count_file_lines(file_path)
|
||||
except Exception as e:
|
||||
logger.error(f"[extractor] Failed to write file after v1 extraction: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to write file: {e}"
|
||||
|
||||
@@ -124,10 +124,13 @@ def store_vectors_subprocess(branch: str, memory_type: str, embeddings: list,
|
||||
|
||||
return json.loads(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[orchestrator] Vector storage operation timed out")
|
||||
return {'success': False, 'error': 'Storage operation timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[orchestrator] Invalid JSON from storage subprocess: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON response: {e}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[orchestrator] Vector storage subprocess error: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -161,10 +164,13 @@ def encode_batch_subprocess(texts: list) -> dict:
|
||||
|
||||
return json.loads(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[orchestrator] Embedding subprocess timed out")
|
||||
return {'success': False, 'error': 'Embedding timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[orchestrator] Invalid JSON from embedding subprocess: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON from embedder: {e}'}
|
||||
except Exception as e:
|
||||
logger.warning(f"[orchestrator] Embedding subprocess error: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -469,8 +475,8 @@ def execute_rollover() -> Dict[str, Any]:
|
||||
success_count=success_count,
|
||||
failed_count=len(failed))
|
||||
logger.info("[rollover] Fired rollover_complete event")
|
||||
except Exception:
|
||||
pass # Trigger system optional
|
||||
except Exception as e:
|
||||
logger.warning(f"[orchestrator] Failed to fire rollover_complete event: {e}")
|
||||
|
||||
# Post-rollover: update central stats
|
||||
try:
|
||||
|
||||
@@ -51,6 +51,7 @@ def normalize_memory_file(file_path: Path, dry_run: bool = False) -> Dict[str, A
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except Exception as e:
|
||||
logger.warning(f"[normalize] Failed to read {file_path}: {e}")
|
||||
return {'success': False, 'error': f"Failed to read: {e}"}
|
||||
|
||||
changes = []
|
||||
@@ -109,8 +110,8 @@ def normalize_memory_file(file_path: Path, dry_run: bool = False) -> Dict[str, A
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
metadata['status']['current_lines'] = len(f.readlines())
|
||||
changes.append("Added current_lines count")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[normalize] Failed to count lines in {file_path}: {e}")
|
||||
|
||||
if 'last_health_check' not in metadata['status']:
|
||||
metadata['status']['last_health_check'] = datetime.now().strftime("%Y-%m-%d")
|
||||
@@ -123,6 +124,7 @@ def normalize_memory_file(file_path: Path, dry_run: bool = False) -> Dict[str, A
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
f.write('\n')
|
||||
except Exception as e:
|
||||
logger.error(f"[normalize] Failed to write {file_path}: {e}")
|
||||
return {'success': False, 'error': f"Failed to write: {e}"}
|
||||
|
||||
json_handler.log_operation("normalize_memory_file", {"file": file_path.name, "changes": len(changes), "success": True})
|
||||
@@ -156,6 +158,7 @@ def normalize_all_memory_files(dry_run: bool = False) -> Dict[str, Any]:
|
||||
registry = json.load(f)
|
||||
branches = registry.get('branches', [])
|
||||
except Exception as e:
|
||||
logger.warning(f"[normalize] Failed to read registry: {e}")
|
||||
return {'success': False, 'error': f"Failed to read registry: {e}"}
|
||||
|
||||
results = {
|
||||
|
||||
@@ -96,10 +96,13 @@ def encode_query_subprocess(query: str) -> dict:
|
||||
'dimension': data.get('dimension', 384)
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[query_executor] Embedding subprocess timed out")
|
||||
return {'success': False, 'error': 'Embedding timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[query_executor] Invalid JSON from embedder: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON from embedder: {e}'}
|
||||
except Exception as e:
|
||||
logger.error(f"[query_executor] Embedding subprocess failed: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
@@ -150,10 +153,13 @@ def search_vectors_subprocess(
|
||||
|
||||
return json.loads(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[query_executor] Search subprocess timed out")
|
||||
return {'success': False, 'error': 'Search operation timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[query_executor] Invalid JSON from search subprocess: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON response: {e}'}
|
||||
except Exception as e:
|
||||
logger.error(f"[query_executor] Search subprocess failed: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ class QueryEncoder:
|
||||
import torch
|
||||
from sentence_transformers import SentenceTransformer
|
||||
except ImportError as e:
|
||||
logger.info(f"[vector_search] Optional ML dependencies not available: {e}")
|
||||
raise ImportError(
|
||||
f"Search requires sentence-transformers and torch. "
|
||||
f"Install with: pip install sentence-transformers torch. "
|
||||
@@ -183,6 +184,7 @@ class SearchService:
|
||||
embedding_function=None
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"[vector_search] Collection lookup failed for '{collection_name}': {e}")
|
||||
return {
|
||||
"collection": collection_name,
|
||||
"exists": False,
|
||||
@@ -325,6 +327,7 @@ def search_collection(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[vector_search] Collection search failed for '{collection_name}': {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Search failed: {e}"
|
||||
@@ -368,6 +371,7 @@ def encode_query(query: str) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[vector_search] Query encoding failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Encoding failed: {e}"
|
||||
@@ -406,6 +410,7 @@ def list_collections(db_path: Path | None = None) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[vector_search] Failed to list collections: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to list collections: {e}"
|
||||
@@ -497,6 +502,7 @@ def search_all_collections(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[vector_search] Multi-collection search failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Multi-collection search failed: {e}"
|
||||
|
||||
@@ -63,6 +63,7 @@ def get_client(db_path: Path):
|
||||
try:
|
||||
import chromadb
|
||||
except ImportError:
|
||||
logger.info("[chroma] chromadb not installed, vector storage unavailable")
|
||||
raise ImportError(
|
||||
"chromadb is required for vector storage. "
|
||||
"Install with: pip install chromadb"
|
||||
@@ -202,7 +203,8 @@ class ChromaService:
|
||||
"exists": True,
|
||||
"vector_count": count
|
||||
}
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[chroma] Collection stats lookup failed for '{collection_name}': {e}")
|
||||
return {
|
||||
"collection": collection_name,
|
||||
"exists": False,
|
||||
@@ -323,6 +325,7 @@ def store_vectors(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma] Vector storage failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Storage failed: {e}"
|
||||
@@ -350,6 +353,7 @@ def get_collection_stats(branch: str, memory_type: str) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma] Failed to get collection stats: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to get stats: {e}"
|
||||
@@ -374,6 +378,7 @@ def list_all_collections() -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma] Failed to list collections: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to list collections: {e}"
|
||||
@@ -399,6 +404,7 @@ def get_database_info() -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma] Failed to get database info: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to get database info: {e}"
|
||||
@@ -489,6 +495,7 @@ def search_vectors(
|
||||
|
||||
except Exception as e:
|
||||
# Collection might not exist - skip it
|
||||
logger.warning(f"[chroma] Skipping collection '{collection_name}' during search: {e}")
|
||||
continue
|
||||
|
||||
# Sort by distance (lower is better)
|
||||
@@ -503,6 +510,7 @@ def search_vectors(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma] Vector search failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Search failed: {e}"
|
||||
|
||||
@@ -20,9 +20,12 @@ Output: JSON on stdout with result
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CHROMADB OPERATIONS (inline — no aipass imports)
|
||||
@@ -122,7 +125,8 @@ def _check_plan(plan_label, db_path=None):
|
||||
collection_name = "flow_flow_plans"
|
||||
try:
|
||||
collection = client.get_collection(collection_name, embedding_function=None)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[chroma_subprocess] Collection '{collection_name}' not found during plan check: {e}")
|
||||
return {
|
||||
'success': True,
|
||||
'found': False,
|
||||
@@ -185,7 +189,8 @@ def _search_vectors(query_embedding, branch=None, memory_type=None, n_results=5,
|
||||
'distance': results['distances'][0][i] if results['distances'] else None,
|
||||
'id': results['ids'][0][i] if results['ids'] else None
|
||||
})
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[chroma_subprocess] Skipping collection '{cname}' during search: {e}")
|
||||
continue
|
||||
|
||||
all_results.sort(key=lambda x: x['distance'] if x['distance'] is not None else float('inf'))
|
||||
@@ -240,6 +245,7 @@ def main():
|
||||
print(json.dumps(result))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma_subprocess] Subprocess operation failed: {e}")
|
||||
print(json.dumps({'success': False, 'error': str(e)}))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ Key Functions:
|
||||
from typing import Dict, Any
|
||||
from pathlib import Path
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
|
||||
@@ -128,6 +129,7 @@ def get_collection(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[chroma_client] Failed to get collection '{collection_name}': {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
|
||||
@@ -24,6 +24,7 @@ import re
|
||||
from typing import Dict, List, Any, Optional
|
||||
from pathlib import Path
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# memory/ root resolved from symbolic/deduplicator.py
|
||||
@@ -153,7 +154,8 @@ def deduplicate_fragment(
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
result = json.loads(resp.read().decode("utf-8"))
|
||||
content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
|
||||
except (urllib.error.URLError, json.JSONDecodeError, KeyError, IndexError):
|
||||
except (urllib.error.URLError, json.JSONDecodeError, KeyError, IndexError) as e:
|
||||
logger.warning(f"[deduplicator] LLM dedup request failed, defaulting to ADD: {e}")
|
||||
return {
|
||||
'success': True,
|
||||
'action': 'ADD',
|
||||
@@ -320,7 +322,8 @@ def _parse_dedup_response(raw_text: str) -> Optional[Dict[str, Any]]:
|
||||
result = json.loads(text)
|
||||
if isinstance(result, dict) and _validate_dedup_result(result):
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning(f"[deduplicator] Direct JSON parse failed, trying fallback: {e}")
|
||||
pass
|
||||
|
||||
# Attempt 2: Strip markdown fences
|
||||
@@ -330,7 +333,8 @@ def _parse_dedup_response(raw_text: str) -> Optional[Dict[str, Any]]:
|
||||
result = json.loads(match.group(1).strip())
|
||||
if isinstance(result, dict) and _validate_dedup_result(result):
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning(f"[deduplicator] Markdown-fenced JSON parse failed, trying fallback: {e}")
|
||||
pass
|
||||
|
||||
# Attempt 3: Find JSON object in text
|
||||
@@ -340,7 +344,8 @@ def _parse_dedup_response(raw_text: str) -> Optional[Dict[str, Any]]:
|
||||
result = json.loads(match.group(0))
|
||||
if isinstance(result, dict) and _validate_dedup_result(result):
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning(f"[deduplicator] Regex-extracted JSON parse failed: {e}")
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
@@ -22,6 +22,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Any, Optional
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
# memory/ root resolved from symbolic/extractor.py
|
||||
@@ -308,7 +309,8 @@ def _parse_llm_json(raw_text: str) -> Optional[List[Dict[str, Any]]]:
|
||||
result = json.loads(text)
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning(f"[extractor] Direct JSON parse failed, trying fallback: {e}")
|
||||
pass
|
||||
# Attempt 2: Strip markdown fences
|
||||
match = re.search(r'```(?:json)?\s*\n?(.*?)\n?\s*```', text, re.DOTALL)
|
||||
@@ -317,7 +319,8 @@ def _parse_llm_json(raw_text: str) -> Optional[List[Dict[str, Any]]]:
|
||||
result = json.loads(match.group(1).strip())
|
||||
if isinstance(result, list):
|
||||
return result
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
except (json.JSONDecodeError, ValueError) as e:
|
||||
logger.warning(f"[extractor] Markdown-fenced JSON parse failed: {e}")
|
||||
pass
|
||||
return None
|
||||
|
||||
@@ -408,7 +411,7 @@ def extract_fragments_llm(chat_history: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
except (urllib.error.URLError, json.JSONDecodeError, KeyError, IndexError) as e:
|
||||
err_msg = f"Chunk {i+1}/{len(chunks)}: {type(e).__name__}: {e}"
|
||||
chunk_errors.append(err_msg)
|
||||
# Error tracked in chunk_errors for module-layer logging
|
||||
logger.warning(f"[extractor] LLM extraction failed for chunk {i+1}/{len(chunks)}: {e}")
|
||||
continue
|
||||
if not content:
|
||||
continue
|
||||
|
||||
@@ -24,6 +24,8 @@ Key Functions:
|
||||
from typing import Dict, List, Any
|
||||
from pathlib import Path
|
||||
|
||||
from aipass.prax import logger
|
||||
|
||||
# Handler imports (domain-organized, no modules)
|
||||
from aipass.memory.apps.handlers.vector import embedder
|
||||
from aipass.memory.apps.handlers.symbolic.chroma_client import get_chroma_client
|
||||
@@ -95,7 +97,8 @@ def search_by_vector(
|
||||
COLLECTION_NAME,
|
||||
embedding_function=None
|
||||
)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[retriever] Collection '{COLLECTION_NAME}' not found for vector search: {e}")
|
||||
return {
|
||||
'success': True,
|
||||
'results': [],
|
||||
@@ -119,6 +122,7 @@ def search_by_vector(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[retriever] Vector search failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Vector search failed: {e}"
|
||||
@@ -163,7 +167,8 @@ def search_by_dimensions(
|
||||
COLLECTION_NAME,
|
||||
embedding_function=None
|
||||
)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[retriever] Collection '{COLLECTION_NAME}' not found for dimension search: {e}")
|
||||
return {
|
||||
'success': True,
|
||||
'results': [],
|
||||
@@ -200,6 +205,7 @@ def search_by_dimensions(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[retriever] Dimension search failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Dimension search failed: {e}"
|
||||
@@ -245,7 +251,8 @@ def search_by_triggers(
|
||||
COLLECTION_NAME,
|
||||
embedding_function=None
|
||||
)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[retriever] Collection '{COLLECTION_NAME}' not found for trigger search: {e}")
|
||||
return {
|
||||
'success': True,
|
||||
'results': [],
|
||||
@@ -300,6 +307,7 @@ def search_by_triggers(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[retriever] Trigger search failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Trigger search failed: {e}"
|
||||
|
||||
@@ -32,6 +32,7 @@ from pathlib import Path
|
||||
# Handler imports (domain-organized, no modules)
|
||||
from aipass.memory.apps.handlers.vector import embedder
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
from aipass.prax import logger
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -308,6 +309,7 @@ def store_fragment(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[storage] Fragment storage failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Storage failed: {e}"
|
||||
@@ -420,6 +422,7 @@ def store_fragments_batch(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[storage] Batch fragment storage failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Batch storage failed: {e}"
|
||||
@@ -540,6 +543,7 @@ def store_llm_fragment(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[storage] LLM fragment storage failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"LLM fragment storage failed: {e}"
|
||||
@@ -668,6 +672,7 @@ def store_llm_fragments_batch(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[storage] Batch LLM fragment storage failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Batch LLM fragment storage failed: {e}"
|
||||
@@ -712,6 +717,7 @@ def delete_fragment(
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[storage] Fragment deletion failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Fragment deletion failed: {e}"
|
||||
|
||||
@@ -223,6 +223,7 @@ def diff_template_vs_branch(branch_path: str | Path) -> dict:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
templates[name] = json.load(f)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"[differ] Failed to load template {path.name}: {e}")
|
||||
result["errors"].append(f"Failed to load template {path.name}: {e}")
|
||||
return result
|
||||
|
||||
@@ -241,6 +242,7 @@ def diff_template_vs_branch(branch_path: str | Path) -> dict:
|
||||
with open(f, 'r', encoding='utf-8') as fh:
|
||||
current = json.load(fh)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"[differ] Failed to read {f.name}: {e}")
|
||||
result["errors"].append(f"Failed to read {f.name}: {e}")
|
||||
continue
|
||||
|
||||
@@ -279,6 +281,7 @@ def diff_template_vs_branch(branch_path: str | Path) -> dict:
|
||||
with open(f, 'r', encoding='utf-8') as fh:
|
||||
current = json.load(fh)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"[differ] Failed to read {f.name}: {e}")
|
||||
result["errors"].append(f"Failed to read {f.name}: {e}")
|
||||
continue
|
||||
|
||||
|
||||
@@ -292,7 +292,8 @@ def _load_registry() -> Optional[List[Dict[str, Any]]]:
|
||||
resolved = _REPO_ROOT / raw_path
|
||||
branch['path'] = str(resolved)
|
||||
return [b for b in branches if b.get("status") == "active"]
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
logger.warning(f"[pusher] Failed to load registry {REGISTRY_PATH}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -326,7 +327,8 @@ def _load_templates() -> Optional[Dict[str, dict]]:
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
templates[name] = json.load(f)
|
||||
except (json.JSONDecodeError, IOError):
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"[pusher] Failed to load template {path}: {e}")
|
||||
return None
|
||||
return templates
|
||||
|
||||
@@ -441,7 +443,8 @@ def _update_version_file(branches_pushed: List[str]) -> bool:
|
||||
json.dump(version_data, f, indent=2, ensure_ascii=False)
|
||||
f.write('\n')
|
||||
return True
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[pusher] Failed to update version file: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -463,7 +466,8 @@ def get_template_status() -> dict:
|
||||
status["version"] = data.get("version")
|
||||
status["last_push"] = data.get("last_push")
|
||||
status["last_push_branches"] = data.get("last_push_branches", [])
|
||||
except (json.JSONDecodeError, IOError):
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
logger.warning(f"[pusher] Failed to read version file: {e}")
|
||||
status["version"] = "error reading version file"
|
||||
return status
|
||||
|
||||
|
||||
@@ -101,7 +101,8 @@ def _read_json(path: Path) -> dict | None:
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
except (json.JSONDecodeError, OSError) as e:
|
||||
logger.warning(f"[spawn_pusher] Failed to read JSON {path}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
@@ -112,7 +113,8 @@ def _write_json(path: Path, data: dict) -> bool:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
return True
|
||||
except OSError:
|
||||
except OSError as e:
|
||||
logger.warning(f"[spawn_pusher] Failed to write JSON {path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,8 @@ def _count_physical_lines(file_path: Path) -> int:
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return len(f.readlines())
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(f"[line_counter] Failed to count lines in {file_path}: {e}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ class EmbeddingService:
|
||||
import torch
|
||||
from sentence_transformers import SentenceTransformer
|
||||
except ImportError as e:
|
||||
logger.info(f"[embedder] Optional ML dependencies not available: {e}")
|
||||
raise ImportError(
|
||||
f"Embedding requires sentence-transformers and torch. "
|
||||
f"Install with: pip install sentence-transformers torch. "
|
||||
@@ -204,6 +205,7 @@ def encode_batch(texts: List[str]) -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[embedder] Batch encoding failed: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Encoding failed: {e}"
|
||||
@@ -284,6 +286,7 @@ def get_model_info() -> Dict[str, Any]:
|
||||
'gpu_enabled': service.use_gpu
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"[embedder] Failed to get model info: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f"Failed to get model info: {e}"
|
||||
|
||||
@@ -342,6 +342,7 @@ if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("[memory] Operation cancelled by user (KeyboardInterrupt)")
|
||||
console.print("\n\nOperation cancelled by user")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
|
||||
@@ -255,6 +255,7 @@ def process_plans_command() -> None:
|
||||
from ..handlers.intake.plans_processor import process_plans
|
||||
result = process_plans()
|
||||
except Exception as e:
|
||||
logger.error(f"[rollover] Plan processing failed: {e}")
|
||||
error(f"Plan processing failed: {e}")
|
||||
return
|
||||
|
||||
|
||||
@@ -97,6 +97,7 @@ def handle_command(command: str, args: List[str]) -> bool:
|
||||
try:
|
||||
n_results = int(args[i + 1])
|
||||
except ValueError:
|
||||
logger.warning(f"[search] Invalid --n argument: {args[i + 1]}")
|
||||
error(f"Invalid number: {args[i + 1]}")
|
||||
return True
|
||||
i += 2
|
||||
|
||||
@@ -169,8 +169,8 @@ def create_fragment(
|
||||
trigger.fire('fragment_created',
|
||||
fragment_id=result['fragment'].get('id'),
|
||||
source_branch=source_branch or 'unknown')
|
||||
except Exception:
|
||||
pass # Trigger optional
|
||||
except Exception as e:
|
||||
logger.warning(f"[symbolic] Trigger fire for fragment_created failed: {e}")
|
||||
return result
|
||||
|
||||
|
||||
@@ -184,8 +184,8 @@ def store_fragment(
|
||||
try:
|
||||
from aipass.trigger.apps.modules.core import trigger
|
||||
trigger.fire('fragment_stored', fragment_id=result.get('fragment_id'))
|
||||
except Exception:
|
||||
pass # Trigger optional
|
||||
except Exception as e:
|
||||
logger.warning(f"[symbolic] Trigger fire for fragment_stored failed: {e}")
|
||||
return result
|
||||
|
||||
|
||||
@@ -328,8 +328,8 @@ def extract_and_store_llm(
|
||||
component="memory",
|
||||
severity="high"
|
||||
)
|
||||
except Exception:
|
||||
pass # Trigger unavailable — prax log is the fallback
|
||||
except Exception as e:
|
||||
logger.warning(f"[symbolic] Error report trigger unavailable: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'processed': 0,
|
||||
@@ -418,6 +418,7 @@ def extract_and_store_llm(
|
||||
skipped += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[symbolic] Fragment processing error: {e}")
|
||||
errors.append(f"Fragment processing error: {e}")
|
||||
|
||||
total_processed = added + updated + skipped
|
||||
@@ -1004,6 +1005,7 @@ def search_fragments_cli(args: List[str]) -> None:
|
||||
try:
|
||||
n_results = int(args[i + 1])
|
||||
except ValueError:
|
||||
logger.warning(f"[symbolic] Invalid --n argument: {args[i + 1]}")
|
||||
console.print(f"[red]Error:[/red] Invalid number: {args[i + 1]}")
|
||||
return
|
||||
i += 2
|
||||
@@ -1408,6 +1410,7 @@ def _parse_jsonl_to_chat_history(jsonl_path: Path) -> List[Dict[str, Any]]:
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
logger.info("[symbolic] Skipping malformed JSONL line during bootstrap parse")
|
||||
continue
|
||||
|
||||
msg_type = entry.get('type', '')
|
||||
@@ -1638,8 +1641,8 @@ def bootstrap_from_jsonl(max_sessions: int = 8) -> None:
|
||||
)
|
||||
col = client.get_collection('symbolic_fragments')
|
||||
console.print(f" [bold green]Collection total: {col.count()} fragments[/bold green]")
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.warning(f"[symbolic] Failed to read ChromaDB collection count: {e}")
|
||||
|
||||
console.print()
|
||||
logger.info(
|
||||
|
||||
@@ -23,6 +23,7 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from aipass.prax import logger
|
||||
from aipass.cli.apps.modules import console, error
|
||||
from aipass.memory.apps.handlers.json import json_handler
|
||||
|
||||
@@ -127,10 +128,13 @@ def _check_plan_subprocess(plan_label: str) -> dict:
|
||||
|
||||
return json.loads(result.stdout)
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("[verify] Plan check subprocess timed out")
|
||||
return {'success': False, 'error': 'Check operation timed out'}
|
||||
except json.JSONDecodeError as e:
|
||||
logger.warning(f"[verify] Invalid JSON from plan check subprocess: {e}")
|
||||
return {'success': False, 'error': f'Invalid JSON response: {e}'}
|
||||
except Exception as e:
|
||||
logger.error(f"[verify] Plan check subprocess failed: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user