From 889621e20e795beb00bcfa79df15cca5e92657e1 Mon Sep 17 00:00:00 2001 From: AIPass Date: Tue, 17 Mar 2026 17:46:50 -0700 Subject: [PATCH] chore(memory,seedgo): memory modernization + seedgo audit fix (#72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Memory: 30 files — symbolic orchestrator, handlers, modules cleanup Seedgo: branch_audit.py fix Co-authored-by: @devpulse --- .../memory/apps/handlers/archive/indexer.py | 3 ++ .../memory/apps/handlers/central_writer.py | 3 ++ .../memory/apps/handlers/dashboard_push.py | 3 ++ .../apps/handlers/intake/plans_processor.py | 3 ++ .../apps/handlers/intake/pool_processor.py | 4 +++ .../memory/apps/handlers/json/memory_files.py | 3 ++ .../memory/apps/handlers/learnings/manager.py | 19 +++++++++++- .../memory/apps/handlers/monitor/detector.py | 1 + .../apps/handlers/monitor/memory_watcher.py | 17 +++++++--- .../apps/handlers/rollover/extractor.py | 12 +++++-- .../apps/handlers/rollover/orchestrator.py | 8 +++++ .../memory/apps/handlers/schema/normalize.py | 3 ++ .../apps/handlers/search/query_executor.py | 2 ++ .../apps/handlers/search/vector_search.py | 6 +++- .../memory/apps/handlers/storage/chroma.py | 3 ++ .../apps/handlers/symbolic/chroma_client.py | 3 ++ .../apps/handlers/symbolic/deduplicator.py | 4 +++ .../apps/handlers/symbolic/extractor.py | 6 +++- .../memory/apps/handlers/symbolic/hook.py | 8 +++-- .../apps/handlers/symbolic/retriever.py | 2 ++ .../memory/apps/handlers/symbolic/storage.py | 21 ++++++++++--- .../memory/apps/handlers/templates/differ.py | 8 +++-- .../memory/apps/handlers/templates/pusher.py | 10 ++++-- .../apps/handlers/templates/spawn_pusher.py | 2 ++ .../apps/handlers/tracking/line_counter.py | 5 ++- .../memory/apps/handlers/vector/embedder.py | 7 +++-- src/aipass/memory/apps/modules/rollover.py | 7 +++++ src/aipass/memory/apps/modules/search.py | 2 ++ src/aipass/memory/apps/modules/symbolic.py | 16 +++++++--- src/aipass/memory/apps/modules/templates.py | 6 +++- .../memory/config/.plans_processed.json | 10 +++++- .../apps/handlers/audit/branch_audit.py | 31 +++---------------- 32 files changed, 179 insertions(+), 59 deletions(-) diff --git a/src/aipass/memory/apps/handlers/archive/indexer.py b/src/aipass/memory/apps/handlers/archive/indexer.py index 49d4c3fd..def8a57a 100644 --- a/src/aipass/memory/apps/handlers/archive/indexer.py +++ b/src/aipass/memory/apps/handlers/archive/indexer.py @@ -25,6 +25,7 @@ from datetime import datetime from typing import Dict, Any, List from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -271,6 +272,8 @@ def check_for_new_files() -> Dict[str, Any]: save_index(index) + log_operation("index_sync", {"new_files": len(new_files), "deleted_files": len(deleted_files), "success": True}) + return { 'success': True, 'new_files': len(new_files), diff --git a/src/aipass/memory/apps/handlers/central_writer.py b/src/aipass/memory/apps/handlers/central_writer.py index 1d09dc08..97897997 100644 --- a/src/aipass/memory/apps/handlers/central_writer.py +++ b/src/aipass/memory/apps/handlers/central_writer.py @@ -24,6 +24,7 @@ from datetime import datetime from typing import Dict, Any from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -259,6 +260,8 @@ def update_central(verbose: bool = False) -> Dict[str, Any]: if verbose: result["stats"] = stats + log_operation("update_central", {"vectors": stats.get("total_vectors", 0), "success": True}) + return result except Exception as e: diff --git a/src/aipass/memory/apps/handlers/dashboard_push.py b/src/aipass/memory/apps/handlers/dashboard_push.py index 173d0d04..718dbd2c 100644 --- a/src/aipass/memory/apps/handlers/dashboard_push.py +++ b/src/aipass/memory/apps/handlers/dashboard_push.py @@ -26,6 +26,7 @@ from datetime import datetime from typing import Dict, Any, List from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -430,6 +431,8 @@ def push_memory_bank_dashboard() -> bool: "memory_bank", section_data, branch_paths ) + log_operation("dashboard_push", {"branches_updated": success_count, "success": success_count > 0}) + return success_count > 0 except Exception: diff --git a/src/aipass/memory/apps/handlers/intake/plans_processor.py b/src/aipass/memory/apps/handlers/intake/plans_processor.py index 0952f9e3..dae73a8e 100644 --- a/src/aipass/memory/apps/handlers/intake/plans_processor.py +++ b/src/aipass/memory/apps/handlers/intake/plans_processor.py @@ -27,6 +27,7 @@ from datetime import datetime from typing import Dict, Any, List from aipass.prax import logger +from aipass.memory.apps.handlers.json.json_handler import log_operation # Subprocess scripts _HANDLERS_DIR = Path(__file__).resolve().parent.parent @@ -321,4 +322,6 @@ def process_plans() -> Dict[str, Any]: if errors: result['errors'] = errors + log_operation("process_plans", {"files_processed": files_processed, "total_chunks": total_chunks, "success": result['success']}) + return result diff --git a/src/aipass/memory/apps/handlers/intake/pool_processor.py b/src/aipass/memory/apps/handlers/intake/pool_processor.py index 4de03ca8..ed522dad 100644 --- a/src/aipass/memory/apps/handlers/intake/pool_processor.py +++ b/src/aipass/memory/apps/handlers/intake/pool_processor.py @@ -34,6 +34,8 @@ from pathlib import Path from datetime import datetime from typing import List, Dict, Any +from aipass.memory.apps.handlers.json.json_handler import log_operation + # Paths _MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent # handlers/intake/ → handlers/ → apps/ → memory/ CONFIG_PATH = _MEMORY_ROOT / "config" / "memory_bank.config.json" @@ -446,6 +448,8 @@ def process_memory_pool() -> dict: if results['files_processed'] > 0: _update_central_and_dashboard() + log_operation("process_memory_pool", {"files_processed": results['files_processed'], "total_chunks": results['total_chunks'], "success": results['success']}) + return results diff --git a/src/aipass/memory/apps/handlers/json/memory_files.py b/src/aipass/memory/apps/handlers/json/memory_files.py index 71ce0e64..4d6ae3a6 100644 --- a/src/aipass/memory/apps/handlers/json/memory_files.py +++ b/src/aipass/memory/apps/handlers/json/memory_files.py @@ -33,6 +33,7 @@ from typing import Dict, Any, Optional from datetime import datetime from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -151,6 +152,8 @@ def write_memory_file(file_path: Path, data: Dict[str, Any]) -> Dict[str, Any]: # Atomic rename (overwrites original) Path(temp_path).rename(file_path) + log_operation("write_memory_file", {"file": file_path.name, "success": True}) + return { 'success': True, 'file': str(file_path) diff --git a/src/aipass/memory/apps/handlers/learnings/manager.py b/src/aipass/memory/apps/handlers/learnings/manager.py index c8bb4cee..154edff5 100644 --- a/src/aipass/memory/apps/handlers/learnings/manager.py +++ b/src/aipass/memory/apps/handlers/learnings/manager.py @@ -34,11 +34,12 @@ from typing import Dict, Any, List, Tuple from datetime import datetime from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() # Handler imports (relative within package) -from aipass.memory.apps.handlers.json.json_handler import ( +from aipass.memory.apps.handlers.json.memory_files import ( read_memory_file_data, write_memory_file_simple ) @@ -493,6 +494,8 @@ def ensure_timestamps(file_path: Path) -> Dict[str, Any]: try: data = read_memory_file_data(file_path) + if data is None: + return {'success': False, 'error': f'Failed to parse file: {file_path.name}'} except Exception as e: return {'success': False, 'error': f'Failed to read file: {e}'} @@ -543,6 +546,8 @@ def enforce_limit(file_path: Path) -> Dict[str, Any]: try: data = read_memory_file_data(file_path) + if data is None: + return {'success': False, 'error': f'Failed to parse file: {file_path.name}'} except Exception as e: return {'success': False, 'error': f'Failed to read file: {e}'} @@ -592,6 +597,8 @@ def enforce_limit(file_path: Path) -> Dict[str, Any]: except Exception as e: return {'success': False, 'error': f'Failed to write file: {e}'} + log_operation("enforce_limit", {"removed": to_remove_count, "remaining": len(to_keep), "success": True}) + return { 'success': True, 'removed': to_remove_count, @@ -623,6 +630,8 @@ def ensure_timestamps_completed(file_path: Path) -> Dict[str, Any]: try: data = read_memory_file_data(file_path) + if data is None: + return {'success': False, 'error': f'Failed to parse file: {file_path.name}'} except Exception as e: return {'success': False, 'error': f'Failed to read file: {e}'} @@ -676,6 +685,8 @@ def enforce_limit_completed(file_path: Path) -> Dict[str, Any]: try: data = read_memory_file_data(file_path) + if data is None: + return {'success': False, 'error': f'Failed to parse file: {file_path.name}'} except Exception as e: return {'success': False, 'error': f'Failed to read file: {e}'} @@ -756,6 +767,8 @@ def add_learning( try: data = read_memory_file_data(file_path) + if data is None: + return {'success': False, 'error': f'Failed to parse file: {file_path.name}'} except Exception as e: return {'success': False, 'error': f'Failed to read file: {e}'} @@ -778,6 +791,8 @@ def add_learning( # Enforce limit after adding enforce_result = enforce_limit(file_path) + log_operation("add_learning", {"key": key, "action": "updated" if is_update else "added", "success": True}) + return { 'success': True, 'action': 'updated' if is_update else 'added', @@ -808,6 +823,8 @@ def update_status_counts(file_path: Path) -> Dict[str, Any]: try: data = read_memory_file_data(file_path) + if data is None: + return {'success': False, 'error': f'Failed to parse file: {file_path.name}'} except Exception as e: return {'success': False, 'error': f'Failed to read file: {e}'} diff --git a/src/aipass/memory/apps/handlers/monitor/detector.py b/src/aipass/memory/apps/handlers/monitor/detector.py index ab9b66c1..a3c7d430 100644 --- a/src/aipass/memory/apps/handlers/monitor/detector.py +++ b/src/aipass/memory/apps/handlers/monitor/detector.py @@ -27,6 +27,7 @@ from typing import List, Dict, Any from dataclasses import dataclass from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() diff --git a/src/aipass/memory/apps/handlers/monitor/memory_watcher.py b/src/aipass/memory/apps/handlers/monitor/memory_watcher.py index 5c0aa44b..6ff6e990 100644 --- a/src/aipass/memory/apps/handlers/monitor/memory_watcher.py +++ b/src/aipass/memory/apps/handlers/monitor/memory_watcher.py @@ -24,7 +24,11 @@ Independence: import logging from pathlib import Path -from typing import Optional, Dict, Any +from typing import TYPE_CHECKING, Optional, Dict, Any + +if TYPE_CHECKING: + from watchdog.observers import Observer as _ObserverType + from watchdog.events import FileSystemEventHandler as _HandlerType try: from watchdog.observers import Observer @@ -33,12 +37,13 @@ try: except ImportError: WATCHDOG_AVAILABLE = False Observer = None - FileSystemEventHandler = object + FileSystemEventHandler = object # type: ignore[assignment,misc] # Handler imports (relative within package) from aipass.memory.apps.handlers.tracking.line_counter import update_line_count from aipass.memory.apps.handlers.monitor.detector import check_single_file from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -46,7 +51,7 @@ logger = get_system_logger() _MEMORY_ROOT = Path(__file__).resolve().parents[3] # Global observer instance -_observer: Optional[Observer] = None +_observer: Any = None # ============================================================================= @@ -222,6 +227,8 @@ def check_and_rollover() -> Dict[str, Any]: # Check code_archive for new files to index results['code_archive'] = _check_code_archive() + log_operation("check_and_rollover", {"files_checked": results.get('files_checked', 0), "rollover_triggered": results.get('rollover_triggered', False)}) + return results @@ -425,7 +432,7 @@ def _is_memory_file(file_path: Path) -> bool: # FILE SYSTEM EVENT HANDLER # ============================================================================= -class MemoryFileWatcher(FileSystemEventHandler): +class MemoryFileWatcher(FileSystemEventHandler): # type: ignore[misc] """Watch for memory file modifications""" def __init__(self): @@ -525,7 +532,7 @@ def start_memory_watcher() -> Dict[str, Any]: watcher = MemoryFileWatcher() # Create observer - new_observer = Observer() + new_observer = Observer() # type: ignore[misc] # Schedule watcher for each branch path (silent - no logging during startup) watched_paths = [] diff --git a/src/aipass/memory/apps/handlers/rollover/extractor.py b/src/aipass/memory/apps/handlers/rollover/extractor.py index 63726f64..a9b0e5cf 100644 --- a/src/aipass/memory/apps/handlers/rollover/extractor.py +++ b/src/aipass/memory/apps/handlers/rollover/extractor.py @@ -33,7 +33,8 @@ from typing import Dict, List, Any, Tuple from datetime import datetime # Handler imports (relative within package) -from aipass.memory.apps.handlers.json.json_handler import read_memory_file_data, write_memory_file_simple +from aipass.memory.apps.handlers.json.json_handler import log_operation +from aipass.memory.apps.handlers.json.memory_files import read_memory_file_data, write_memory_file_simple from aipass.prax.apps.modules.logger import get_system_logger logger = get_system_logger() @@ -125,8 +126,8 @@ def restore_from_backup(file_path: Path) -> Dict[str, Any]: # FILE OPERATIONS # ============================================================================= -def _read_memory_file(file_path: Path) -> Dict[str, Any]: - """Read memory JSON file using json_handler""" +def _read_memory_file(file_path: Path) -> Dict[str, Any] | None: + """Read memory JSON file using memory_files handler.""" return read_memory_file_data(file_path) @@ -380,6 +381,11 @@ def extract_items( # Read file try: data = _read_memory_file(file_path) + if data is None: + return { + 'success': False, + 'error': f"Failed to parse memory file: {file_path.name}" + } current_lines = _count_file_lines(file_path) except Exception as e: return { diff --git a/src/aipass/memory/apps/handlers/rollover/orchestrator.py b/src/aipass/memory/apps/handlers/rollover/orchestrator.py index 9d2182e4..5a3daa5c 100644 --- a/src/aipass/memory/apps/handlers/rollover/orchestrator.py +++ b/src/aipass/memory/apps/handlers/rollover/orchestrator.py @@ -26,6 +26,7 @@ from pathlib import Path from typing import List, Dict, Any from aipass.prax import logger +from aipass.memory.apps.handlers.json.json_handler import log_operation # logger imported from aipass.prax @@ -502,6 +503,13 @@ def execute_rollover() -> Dict[str, Any]: except Exception as e: logger.info(f"[rollover] Memory pool check: {e}") + log_operation("rollover_execute", { + "triggers": len(triggers), + "success_count": success_count, + "failed_count": len(failed), + "success": success_count > 0 or len(triggers) == 0, + }) + return { 'success': success_count > 0 or len(triggers) == 0, 'triggers_count': len(triggers), diff --git a/src/aipass/memory/apps/handlers/schema/normalize.py b/src/aipass/memory/apps/handlers/schema/normalize.py index f8672db6..b24a5290 100644 --- a/src/aipass/memory/apps/handlers/schema/normalize.py +++ b/src/aipass/memory/apps/handlers/schema/normalize.py @@ -28,6 +28,7 @@ from typing import Dict, Any from datetime import datetime from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -124,6 +125,8 @@ def normalize_memory_file(file_path: Path, dry_run: bool = False) -> Dict[str, A except Exception as e: return {'success': False, 'error': f"Failed to write: {e}"} + log_operation("normalize_memory_file", {"file": file_path.name, "changes": len(changes), "success": True}) + return { 'success': True, 'file': str(file_path), diff --git a/src/aipass/memory/apps/handlers/search/query_executor.py b/src/aipass/memory/apps/handlers/search/query_executor.py index 0fcb46e6..c5c0e27e 100644 --- a/src/aipass/memory/apps/handlers/search/query_executor.py +++ b/src/aipass/memory/apps/handlers/search/query_executor.py @@ -26,6 +26,7 @@ from pathlib import Path from typing import Dict, Any from aipass.prax import logger +from aipass.memory.apps.handlers.json.json_handler import log_operation # Subprocess scripts for ML operations (run in memory venv) _HANDLERS_DIR = Path(__file__).resolve().parent.parent @@ -274,6 +275,7 @@ def execute_search( logger.info(f"[search] Filtered to {len(filtered_results)} relevant results") + log_operation("search_execute", {"query_len": len(query), "results": len(filtered_results), "success": True}) return { 'success': True, 'query': query, diff --git a/src/aipass/memory/apps/handlers/search/vector_search.py b/src/aipass/memory/apps/handlers/search/vector_search.py index b53261de..fa000510 100644 --- a/src/aipass/memory/apps/handlers/search/vector_search.py +++ b/src/aipass/memory/apps/handlers/search/vector_search.py @@ -33,6 +33,7 @@ from typing import List, Dict, Any from pathlib import Path from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -317,6 +318,7 @@ def search_collection( 'collection': collection_name } + log_operation("vector_search_collection", {"collection": collection_name, "count": result.get('count', 0), "success": True}) return { 'success': True, **result @@ -485,11 +487,13 @@ def search_all_collections( 'count': coll_result['count'] } + total = sum(r['count'] for r in results.values()) + log_operation("vector_search_all", {"collections": len(results), "total_results": total, "success": True}) return { 'success': True, 'results': results, 'collections_searched': len(results), - 'total_results': sum(r['count'] for r in results.values()) + 'total_results': total } except Exception as e: diff --git a/src/aipass/memory/apps/handlers/storage/chroma.py b/src/aipass/memory/apps/handlers/storage/chroma.py index 6b915873..d67e8e4a 100644 --- a/src/aipass/memory/apps/handlers/storage/chroma.py +++ b/src/aipass/memory/apps/handlers/storage/chroma.py @@ -33,6 +33,7 @@ from pathlib import Path from datetime import datetime from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -315,6 +316,7 @@ def store_vectors( service = _get_service(db_path) result = service.store_vectors(branch, memory_type, embeddings, documents, metadatas) + log_operation("chroma_store_vectors", {"collection": result.get('collection'), "count": result.get('count', 0), "success": True}) return { 'success': True, **result @@ -492,6 +494,7 @@ def search_vectors( # Sort by distance (lower is better) all_results.sort(key=lambda x: x['distance'] if x['distance'] is not None else float('inf')) + log_operation("chroma_search_vectors", {"collections": len(collection_names), "results": len(all_results), "success": True}) return { 'success': True, 'results': all_results[:n_results * len(collection_names)] if all_results else [], diff --git a/src/aipass/memory/apps/handlers/symbolic/chroma_client.py b/src/aipass/memory/apps/handlers/symbolic/chroma_client.py index e9f0bb25..c78a1d6f 100644 --- a/src/aipass/memory/apps/handlers/symbolic/chroma_client.py +++ b/src/aipass/memory/apps/handlers/symbolic/chroma_client.py @@ -34,6 +34,8 @@ Key Functions: from typing import Dict, Any from pathlib import Path +from aipass.memory.apps.handlers.json.json_handler import log_operation + # ============================================================================= # CONSTANTS @@ -128,6 +130,7 @@ def get_collection( embedding_function=None ) + log_operation("chroma_get_collection", {"collection": collection_name, "create": create, "success": True}) return { 'success': True, 'collection': collection diff --git a/src/aipass/memory/apps/handlers/symbolic/deduplicator.py b/src/aipass/memory/apps/handlers/symbolic/deduplicator.py index ee1c9e63..d5f5683d 100644 --- a/src/aipass/memory/apps/handlers/symbolic/deduplicator.py +++ b/src/aipass/memory/apps/handlers/symbolic/deduplicator.py @@ -32,6 +32,8 @@ import re from typing import Dict, List, Any, Optional from pathlib import Path +from aipass.memory.apps.handlers.json.json_handler import log_operation + # memory/ root resolved from symbolic/deduplicator.py _MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent @@ -189,6 +191,8 @@ def deduplicate_fragment( action = parsed['action'] reason = parsed.get('reason', 'No reason provided') + log_operation("symbolic_dedup", {"action": action, "existing_count": len(existing_fragments), "success": True}) + # Apply action to fragment if action == 'UPDATE': # Merge content from LLM response into fragment diff --git a/src/aipass/memory/apps/handlers/symbolic/extractor.py b/src/aipass/memory/apps/handlers/symbolic/extractor.py index 322307c0..151aae3d 100644 --- a/src/aipass/memory/apps/handlers/symbolic/extractor.py +++ b/src/aipass/memory/apps/handlers/symbolic/extractor.py @@ -31,6 +31,8 @@ from datetime import datetime from pathlib import Path from typing import Dict, List, Any, Optional +from aipass.memory.apps.handlers.json.json_handler import log_operation + # memory/ root resolved from symbolic/extractor.py _MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent @@ -450,7 +452,7 @@ def analyze_conversation_llm(chat_history: List[Dict[str, Any]]) -> Dict[str, An 'message_count': 0, 'error': None} llm = extract_fragments_llm(chat_history) reg = analyze_conversation(chat_history) - return { + result = { 'success': llm.get('success', False), 'fragments': llm.get('fragments', []), 'metadata': { @@ -462,3 +464,5 @@ def analyze_conversation_llm(chat_history: List[Dict[str, Any]]) -> Dict[str, An 'chunk_count': llm.get('chunk_count', 0)}, 'message_count': reg.get('message_count', len(chat_history)), 'error': llm.get('error')} + log_operation("symbolic_extract", {"fragments": len(result['fragments']), "messages": result['message_count'], "success": True}) + return result diff --git a/src/aipass/memory/apps/handlers/symbolic/hook.py b/src/aipass/memory/apps/handlers/symbolic/hook.py index 6c1bb572..964c10ac 100644 --- a/src/aipass/memory/apps/handlers/symbolic/hook.py +++ b/src/aipass/memory/apps/handlers/symbolic/hook.py @@ -36,7 +36,8 @@ from typing import Dict, List, Any, Tuple # Handler imports (domain-organized, no modules) from aipass.memory.apps.handlers.symbolic import retriever -from aipass.memory.apps.handlers.json import json_handler +from aipass.memory.apps.handlers.json import memory_files +from aipass.memory.apps.handlers.json.json_handler import log_operation # memory/ root resolved from symbolic/hook.py _MEMORY_ROOT = Path(__file__).resolve().parent.parent.parent.parent @@ -82,7 +83,7 @@ def load_config(config_path: Path | None = None) -> Dict[str, Any]: path = config_path or DEFAULT_CONFIG_PATH if path.exists(): - result = json_handler.read_memory_file(path) + result = memory_files.read_memory_file(path) if result.get('success'): config = result.get('data', {}) # Merge with defaults for any missing keys @@ -107,7 +108,7 @@ def save_config(config: Dict[str, Any], config_path: Path | None = None) -> Dict # Ensure parent directory exists path.parent.mkdir(parents=True, exist_ok=True) - return json_handler.write_memory_file(path, config) + return memory_files.write_memory_file(path, config) # ============================================================================= @@ -634,6 +635,7 @@ def process_hook( # Record the surface record_surface(fragment) + log_operation("symbolic_hook", {"fragment_id": fragment.get('id'), "surfaced": True, "success": True}) return { 'success': True, 'surfaced': True, diff --git a/src/aipass/memory/apps/handlers/symbolic/retriever.py b/src/aipass/memory/apps/handlers/symbolic/retriever.py index 3aa6dae1..e910042b 100644 --- a/src/aipass/memory/apps/handlers/symbolic/retriever.py +++ b/src/aipass/memory/apps/handlers/symbolic/retriever.py @@ -38,6 +38,7 @@ from pathlib import Path # 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 +from aipass.memory.apps.handlers.json.json_handler import log_operation # ============================================================================= @@ -402,6 +403,7 @@ def retrieve_fragments( # Return top n_results final_results = ranked[:n_results] + log_operation("symbolic_retrieve", {"results": len(final_results), "methods": search_methods_used, "success": True}) return { 'success': True, 'results': final_results, diff --git a/src/aipass/memory/apps/handlers/symbolic/storage.py b/src/aipass/memory/apps/handlers/symbolic/storage.py index c20f0a86..12c154c7 100644 --- a/src/aipass/memory/apps/handlers/symbolic/storage.py +++ b/src/aipass/memory/apps/handlers/symbolic/storage.py @@ -40,6 +40,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.json_handler import log_operation # ============================================================================= @@ -306,11 +307,13 @@ def store_fragment( metadatas=[flat_meta] ) + total = collection.count() + log_operation("symbolic_store_fragment", {"fragment_id": frag_id, "total": total, "success": True}) return { 'success': True, 'fragment_id': frag_id, 'collection': COLLECTION_NAME, - 'total_fragments': collection.count() + 'total_fragments': total } except Exception as e: @@ -416,11 +419,13 @@ def store_fragments_batch( metadatas=batch_metadatas ) + total = collection.count() + log_operation("symbolic_store_batch", {"stored": len(batch_ids), "total": total, "success": True}) return { 'success': True, 'stored': len(batch_ids), 'collection': COLLECTION_NAME, - 'total_fragments': collection.count() + 'total_fragments': total } except Exception as e: @@ -534,11 +539,13 @@ def store_llm_fragment( metadatas=[flat_meta] ) + total = collection.count() + log_operation("symbolic_store_llm_fragment", {"fragment_id": frag_id, "total": total, "success": True}) return { 'success': True, 'fragment_id': frag_id, 'collection': COLLECTION_NAME, - 'total_fragments': collection.count() + 'total_fragments': total } except Exception as e: @@ -660,11 +667,13 @@ def store_llm_fragments_batch( metadatas=frag_metas ) + total = collection.count() + log_operation("symbolic_store_llm_batch", {"stored": len(frag_ids), "total": total, "success": True}) return { 'success': True, 'stored': len(frag_ids), 'collection': COLLECTION_NAME, - 'total_fragments': collection.count() + 'total_fragments': total } except Exception as e: @@ -703,10 +712,12 @@ def delete_fragment( collection.delete(ids=[fragment_id]) + total = collection.count() + log_operation("symbolic_delete_fragment", {"deleted_id": fragment_id, "total": total, "success": True}) return { 'success': True, 'deleted_id': fragment_id, - 'total_fragments': collection.count() + 'total_fragments': total } except Exception as e: diff --git a/src/aipass/memory/apps/handlers/templates/differ.py b/src/aipass/memory/apps/handlers/templates/differ.py index 82120d4c..d0ad2499 100644 --- a/src/aipass/memory/apps/handlers/templates/differ.py +++ b/src/aipass/memory/apps/handlers/templates/differ.py @@ -28,6 +28,7 @@ from pathlib import Path from typing import Dict, Any, List, Optional from aipass.prax import logger +from aipass.memory.apps.handlers.json.json_handler import log_operation # ============================================================================= # PATH SETUP @@ -65,7 +66,7 @@ def _replace_placeholders(template: dict, branch_name: str) -> dict: today = datetime.now().strftime("%Y-%m-%d") result = copy.deepcopy(template) - def _replace_in_value(val): + def _replace_in_value(val: Any) -> Any: if isinstance(val, str): return val.replace("{{BRANCHNAME}}", branch_name).replace("{{DATE}}", today) elif isinstance(val, list): @@ -74,7 +75,9 @@ def _replace_placeholders(template: dict, branch_name: str) -> dict: return {k: _replace_in_value(v) for k, v in val.items()} return val - return _replace_in_value(result) + replaced = _replace_in_value(result) + assert isinstance(replaced, dict) + return replaced # ============================================================================= @@ -295,6 +298,7 @@ def diff_template_vs_branch(branch_path: str | Path) -> dict: if file_diff["additions"] or file_diff["removals"] or file_diff["modifications"]: result["observations"].append(file_diff) + log_operation("template_diff", {"branch": branch_name, "local_diffs": len(result["local"]), "obs_diffs": len(result["observations"]), "success": True}) return result diff --git a/src/aipass/memory/apps/handlers/templates/pusher.py b/src/aipass/memory/apps/handlers/templates/pusher.py index 71ecf4c3..6044cbe5 100644 --- a/src/aipass/memory/apps/handlers/templates/pusher.py +++ b/src/aipass/memory/apps/handlers/templates/pusher.py @@ -30,9 +30,10 @@ from typing import Dict, Any, List, Tuple, Optional from datetime import datetime from aipass.prax import logger +from aipass.memory.apps.handlers.json.json_handler import log_operation # Handler imports (same-branch allowed per handler boundaries) -from aipass.memory.apps.handlers.json.json_handler import ( +from aipass.memory.apps.handlers.json.memory_files import ( read_memory_file_data, write_memory_file_simple ) @@ -81,7 +82,7 @@ def _replace_placeholders(template: dict, branch_name: str) -> dict: """Replace {{BRANCHNAME}} and {{DATE}} in template values. Returns new dict.""" today = datetime.now().strftime("%Y-%m-%d") - def _walk(val): + def _walk(val: Any) -> Any: if isinstance(val, str): return val.replace("{{BRANCHNAME}}", branch_name).replace("{{DATE}}", today) elif isinstance(val, list): @@ -90,7 +91,9 @@ def _replace_placeholders(template: dict, branch_name: str) -> dict: return {k: _walk(v) for k, v in val.items()} return val - return _walk(copy.deepcopy(template)) + result = _walk(copy.deepcopy(template)) + assert isinstance(result, dict) + return result # ============================================================================= @@ -417,6 +420,7 @@ def push_templates(dry_run: bool = False) -> dict: if not _update_version_file(result["branches_list"]): result["errors"].append("Failed to update template version file") + log_operation("template_push", {"branches": result["branches_updated"], "files": result["files_modified"], "dry_run": dry_run, "success": True}) return result diff --git a/src/aipass/memory/apps/handlers/templates/spawn_pusher.py b/src/aipass/memory/apps/handlers/templates/spawn_pusher.py index c365cde4..408dff85 100644 --- a/src/aipass/memory/apps/handlers/templates/spawn_pusher.py +++ b/src/aipass/memory/apps/handlers/templates/spawn_pusher.py @@ -29,6 +29,7 @@ from pathlib import Path from typing import Any, Dict, List from aipass.prax import logger +from aipass.memory.apps.handlers.json.json_handler import log_operation # ============================================================================= # PATH SETUP @@ -229,6 +230,7 @@ def push_to_spawn_templates(dry_run: bool = False) -> dict: f"{result['files_modified']} files modified" ) + log_operation("spawn_template_push", {"sets_updated": result["template_sets_updated"], "files": result["files_modified"], "dry_run": dry_run, "success": True}) return result diff --git a/src/aipass/memory/apps/handlers/tracking/line_counter.py b/src/aipass/memory/apps/handlers/tracking/line_counter.py index 6d6ad974..85547ce9 100644 --- a/src/aipass/memory/apps/handlers/tracking/line_counter.py +++ b/src/aipass/memory/apps/handlers/tracking/line_counter.py @@ -26,8 +26,9 @@ from typing import Dict, Any from datetime import datetime # Handler imports (relative within package) -from aipass.memory.apps.handlers.json.json_handler import update_metadata +from aipass.memory.apps.handlers.json.memory_files import update_metadata from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -91,6 +92,8 @@ def update_line_count(file_path: Path) -> Dict[str, Any]: 'error': f"Failed to update metadata: {result['error']}" } + log_operation("update_line_count", {"file": file_path.name, "lines": line_count, "success": True}) + return { 'success': True, 'file': str(file_path), diff --git a/src/aipass/memory/apps/handlers/vector/embedder.py b/src/aipass/memory/apps/handlers/vector/embedder.py index a6ad481f..f6001de5 100644 --- a/src/aipass/memory/apps/handlers/vector/embedder.py +++ b/src/aipass/memory/apps/handlers/vector/embedder.py @@ -33,6 +33,7 @@ from typing import List, Dict, Any from pathlib import Path from aipass.prax.apps.modules.logger import get_system_logger +from aipass.memory.apps.handlers.json.json_handler import log_operation logger = get_system_logger() @@ -117,11 +118,12 @@ class EmbeddingService: # Pre-sort by length (reduces padding waste by 30%) sorted_pairs = sorted(enumerate(texts), key=lambda x: len(x[1])) - sorted_indices, sorted_texts = zip(*sorted_pairs) + sorted_indices: list[int] = [p[0] for p in sorted_pairs] + sorted_text_list: list[str] = [p[1] for p in sorted_pairs] # Encode with optimal settings embeddings = self.model.encode( - sorted_texts, + sorted_text_list, batch_size=self.batch_size, convert_to_tensor=False, # Return numpy for Chroma normalize_embeddings=True, # Critical for L2 distance @@ -195,6 +197,7 @@ def encode_batch(texts: List[str]) -> Dict[str, Any]: service = _get_service() result = service.encode_batch(texts) + log_operation("vector_encode_batch", {"count": result.get('count', 0), "dimension": result.get('dimension', 0), "success": True}) return { 'success': True, **result diff --git a/src/aipass/memory/apps/modules/rollover.py b/src/aipass/memory/apps/modules/rollover.py index 74aa524c..ccf6d77d 100755 --- a/src/aipass/memory/apps/modules/rollover.py +++ b/src/aipass/memory/apps/modules/rollover.py @@ -29,6 +29,7 @@ from rich import box from aipass.prax import logger from aipass.cli.apps.modules import console, error, warning +from aipass.memory.apps.handlers.json.json_handler import log_operation # ============================================================================= # INFRASTRUCTURE SETUP @@ -221,6 +222,7 @@ def run_rollover() -> bool: for fail in failed: error(f"{fail['trigger']} - {fail['stage']}: {fail['error']}") + log_operation("rollover_execute", {"triggers": triggers_count, "success_count": success_count}) return success_count > 0 @@ -253,6 +255,7 @@ def sync_line_counts() -> None: warning(f"{result['failed']} files failed") for branch, mem_type, err_msg in result.get('failures', []): error(f"{branch}.{mem_type}: {err_msg}") + log_operation("rollover_sync_lines", {"updated": result['updated'], "failed": result['failed']}) else: error("Failed to sync line counts") @@ -325,6 +328,8 @@ def show_status() -> None: console.print() + log_operation("rollover_status", {"branches_checked": stats['total_branches'], "files_ready": stats['files_ready']}) + def check_triggers() -> None: """ @@ -351,6 +356,7 @@ def check_triggers() -> None: if not triggers: console.print("[green]>[/green] No files need rollover") + log_operation("rollover_check", {"files_needing_rollover": 0}) return console.print(f"[bold cyan]Found {len(triggers)} files ready for rollover:[/bold cyan]") @@ -362,6 +368,7 @@ def check_triggers() -> None: console.print() console.print("[dim]Run 'drone @memory rollover' to process these files[/dim]") console.print() + log_operation("rollover_check", {"files_needing_rollover": len(triggers)}) # ============================================================================= diff --git a/src/aipass/memory/apps/modules/search.py b/src/aipass/memory/apps/modules/search.py index 2897ad6d..71f4643e 100755 --- a/src/aipass/memory/apps/modules/search.py +++ b/src/aipass/memory/apps/modules/search.py @@ -28,6 +28,7 @@ from rich import box from aipass.prax import logger from aipass.cli.apps.modules import console, error, warning +from aipass.memory.apps.handlers.json.json_handler import log_operation # ============================================================================= # INFRASTRUCTURE SETUP @@ -256,6 +257,7 @@ def show_search_results( )) console.print() + log_operation("search_query", {"query": query, "results": len(filtered_results)}) return True diff --git a/src/aipass/memory/apps/modules/symbolic.py b/src/aipass/memory/apps/modules/symbolic.py index 86ad854d..f544b1a0 100644 --- a/src/aipass/memory/apps/modules/symbolic.py +++ b/src/aipass/memory/apps/modules/symbolic.py @@ -35,6 +35,7 @@ from typing import List, Dict, Any # Service imports from aipass.prax import logger from aipass.cli.apps.modules import console, header +from aipass.memory.apps.handlers.json.json_handler import log_operation # Handler imports (domain-organized) from aipass.memory.apps.handlers.symbolic import extractor @@ -980,6 +981,7 @@ def run_demo() -> None: console.print("[dim]Note: Run 'symbolic extract ' to use the real LLM pipeline[/dim]") console.print() + log_operation("symbolic_demo", {"success": True}) def search_fragments_cli(args: List[str]) -> None: @@ -1137,6 +1139,7 @@ def search_fragments_cli(args: List[str]) -> None: console.print() logger.info(f"[symbolic] Displayed {len(results)} fragment results") + log_operation("symbolic_fragments", {"query": query or "", "results": len(results)}) def run_hook_test(args: List[str]) -> None: @@ -1267,18 +1270,19 @@ def run_hook_test(args: List[str]) -> None: console.print(f" [dim]Fragments surfaced:[/dim] {state.get('fragments_surfaced', 0)}") console.print(f" [dim]Messages since last:[/dim] {state.get('messages_since_last', 0)}") console.print() + log_operation("symbolic_hook_test", {"surfaced": result.get('surfaced', False), "success": result.get('success', False)}) def analyze_file(file_path: str) -> None: """Analyze a conversation JSON file""" - from aipass.memory.apps.handlers.json import json_handler + from aipass.memory.apps.handlers.json import memory_files path = Path(file_path) if not path.exists(): console.print(f"[red]Error:[/red] File not found: {file_path}") return - read_result = json_handler.read_memory_file(path) + read_result = memory_files.read_memory_file(path) if not read_result.get('success'): console.print(f"[red]Error:[/red] {read_result.get('error', 'Failed to read JSON')}") return @@ -1315,6 +1319,7 @@ def analyze_file(file_path: str) -> None: console.print(f" [dim]Words:[/dim] {meta.get('total_words', 0)}") console.print(f" [dim]Depth:[/dim] {meta.get('depth', 'unknown')}") console.print() + log_operation("symbolic_analyze", {"file": path.name, "messages": result['message_count']}) else: console.print(f"[red]✗[/red] Analysis failed: {result.get('error', 'Unknown error')}") @@ -1330,14 +1335,14 @@ def extract_file(file_path: str, source_branch: str | None = None) -> None: file_path: Path to JSON conversation file source_branch: Optional branch name tag for stored fragments """ - from aipass.memory.apps.handlers.json import json_handler + from aipass.memory.apps.handlers.json import memory_files path = Path(file_path) if not path.exists(): console.print(f"[red]Error:[/red] File not found: {file_path}") return - read_result = json_handler.read_memory_file(path) + read_result = memory_files.read_memory_file(path) if not read_result.get('success'): console.print(f"[red]Error:[/red] {read_result.get('error', 'Failed to read JSON')}") return @@ -1380,6 +1385,8 @@ def extract_file(file_path: str, source_branch: str | None = None) -> None: console.print() logger.info(f"[symbolic] extract_file complete: {result}") + if result.get('success'): + log_operation("symbolic_extract", {"file": path.name, "added": result.get('added', 0), "updated": result.get('updated', 0)}) # ============================================================================= @@ -1650,6 +1657,7 @@ def bootstrap_from_jsonl(max_sessions: int = 8) -> None: f"{total_added} added, {total_updated} updated, " f"{total_skipped} skipped, {total_errors} errors" ) + log_operation("symbolic_bootstrap", {"sessions": processed_count, "added": total_added, "errors": total_errors}) # ============================================================================= diff --git a/src/aipass/memory/apps/modules/templates.py b/src/aipass/memory/apps/modules/templates.py index 12eee84a..34c4cd48 100644 --- a/src/aipass/memory/apps/modules/templates.py +++ b/src/aipass/memory/apps/modules/templates.py @@ -28,6 +28,7 @@ from rich import box from aipass.prax import logger from aipass.cli.apps.modules import console, error, warning +from aipass.memory.apps.handlers.json.json_handler import log_operation # ============================================================================= # INFRASTRUCTURE SETUP @@ -37,7 +38,7 @@ from aipass.cli.apps.modules import console, error, warning from aipass.memory.apps.handlers.templates.pusher import push_templates, get_template_status from aipass.memory.apps.handlers.templates.differ import diff_template_vs_branch from aipass.memory.apps.handlers.templates.spawn_pusher import push_to_spawn_templates -from aipass.memory.apps.handlers.json.json_handler import read_memory_file_data +from aipass.memory.apps.handlers.json.memory_files import read_memory_file_data def _find_repo_root() -> Path: @@ -286,6 +287,7 @@ def _display_push_results(result: dict, dry_run: bool) -> None: f"{result['branches_updated']}/{result['branches_scanned']} branches, " f"{result['files_modified']} files" ) + log_operation("templates_push", {"branches_updated": result['branches_updated'], "files_modified": result['files_modified']}) console.print() @@ -424,6 +426,7 @@ def _display_diff_results(branch_name: str | None = None) -> None: console.print(f"[red]{total_errors} errors encountered[/red]") logger.info(f"[templates] Diff complete: {total_diffs} branches with diffs, {total_errors} errors") + log_operation("templates_diff", {"branches_compared": len(branches), "branches_with_diffs": total_diffs}) console.print() @@ -481,6 +484,7 @@ def _display_status(status: dict) -> None: console.print("[cyan]Branches pushed:[/cyan] none") logger.info(f"[templates] Status checked - version: {version}, last push: {last_push}") + log_operation("templates_status", {"version": version, "branches_pushed": len(pushed)}) console.print() diff --git a/src/aipass/memory/config/.plans_processed.json b/src/aipass/memory/config/.plans_processed.json index 8e4e889a..d38d0e69 100644 --- a/src/aipass/memory/config/.plans_processed.json +++ b/src/aipass/memory/config/.plans_processed.json @@ -1,3 +1,11 @@ { - "FPLAN-0025_build_status_board_per_branch_statusloca_2026-03-10.md": "2026-03-12T16:49:45.098626" + "FPLAN-0025_build_status_board_per_branch_statusloca_2026-03-10.md": "2026-03-12T16:49:45.098626", + "FPLAN-0033_cli_stderr_migration_phase_3_remaining_1_2026-03-13.md": "2026-03-17T17:03:38.975570", + "FPLAN-0040_phase_1_dashboard_cleanup_remove_devpuls_2026-03-14.md": "2026-03-17T17:03:49.794023", + "FPLAN-0049_memory_search_introspection_2026-03-15.md": "2026-03-17T17:04:00.894613", + "FPLAN-0041_migrate_init_projectpy_to_cli_branch_2026-03-14.md": "2026-03-17T17:04:11.390219", + "FPLAN-0032_cli_stderr_standardization_phase_1_add_e_2026-03-13.md": "2026-03-17T17:04:20.978306", + "FPLAN-0034_fplan_0033_wave_1_stderr_migration_api_s_2026-03-14.md": "2026-03-17T17:04:31.760541", + "FPLAN-0031_drone_stderr_and_error_propagation_inves_2026-03-13.md": "2026-03-17T17:04:42.782966", + "FPLAN-0047_memory_rollover_introspection_2026-03-15.md": "2026-03-17T17:04:54.278205" } \ No newline at end of file diff --git a/src/aipass/seedgo/apps/handlers/audit/branch_audit.py b/src/aipass/seedgo/apps/handlers/audit/branch_audit.py index 17c83037..08c667e5 100644 --- a/src/aipass/seedgo/apps/handlers/audit/branch_audit.py +++ b/src/aipass/seedgo/apps/handlers/audit/branch_audit.py @@ -9,11 +9,11 @@ import importlib.util from pathlib import Path -from typing import Dict, List +from typing import Any, Dict, List from aipass.seedgo.apps.handlers.bypass import ignore_handler from aipass.seedgo.apps.handlers.json import json_handler -def discover_checkers(pack_path: Path = None) -> Dict[str, object]: +def discover_checkers(pack_path: Path | None = None) -> Dict[str, Any]: """Auto-discover all *_check.py modules from a pack directory. Args: @@ -76,35 +76,14 @@ def _log_structure_post_checks(branch_path: Path) -> tuple: - ``system_logs/`` at repo root is managed by prax (runtime dispatch). Having many system logs and few local logs is *normal*. - ``logs/`` at branch root holds local-only logs. Flat placement is - fine -- hierarchical sub-directories are only expected when there - are enough logs to warrant organisation (>5). + fine — the standard does not prescribe internal organisation. """ violations: list[dict] = [] scores: list[int] = [] - root_logs_dir = branch_path / "logs" in_dirs = [f for f in branch_path.rglob("*.log") if f.parent.name == "logs"] - # Check 1: If the branch has a logs/ directory, verify it is not empty - # and only flag flat placement when there are enough files to warrant - # hierarchical organisation. - if root_logs_dir.is_dir(): - root_only = [f for f in in_dirs if f.parent == root_logs_dir] - has_subdirs = any(f.parent != root_logs_dir for f in in_dirs) - if not root_only and not has_subdirs: - # logs/ exists but is empty -- acceptable (no violation) - scores.append(100) - elif len(root_only) > 5 and not has_subdirs: - # Many logs piled at root with no subdirectories - scores.append(50) - violations.append({ - "file": "(branch-level)", "path": str(root_logs_dir), "score": 50, - "issues": [f"{len(root_only)} logs at branch root with no sub-directories — consider hierarchical placement"], - }) - else: - scores.append(100) - - # Check 2: Verify system_logs/ exists when the branch produces logs. + # Check: Verify system_logs/ exists when the branch produces logs. # The two-tier model expects prax to dispatch runtime logs to # system_logs/. A mismatch only matters when the branch has NO # system logs at all despite having local logs (potential prax @@ -146,7 +125,7 @@ def _load_diagnostics_checker(): return mod -def audit_branch(branch: Dict[str, str], bypass_rules: list, pack_path: Path = None) -> Dict: +def audit_branch(branch: Dict[str, str], bypass_rules: list, pack_path: Path | None = None) -> Dict: """Audit a branch for standards compliance. Returns backward-compatible dict.""" entry_file, branch_path = branch["entry_file"], Path(branch["path"]) checkers, all_files = discover_checkers(pack_path), _collect_py_files(branch_path)