From 4a130ec9541658082d82fe75d4ef34ccb1ebd222 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Wed, 18 Mar 2026 22:13:56 -0700 Subject: [PATCH] feat(memory): config fix + verify command for plan vectorization Co-Authored-By: @memory --- .../memory/apps/handlers/central_writer.py | 13 +- .../memory/apps/handlers/dashboard_push.py | 2 +- .../handlers/storage/chroma_subprocess.py | 48 +++ src/aipass/memory/apps/modules/verify.py | 285 ++++++++++++++++++ .../memory/config/memory_bank.config.json | 2 +- 5 files changed, 347 insertions(+), 3 deletions(-) create mode 100644 src/aipass/memory/apps/modules/verify.py diff --git a/src/aipass/memory/apps/handlers/central_writer.py b/src/aipass/memory/apps/handlers/central_writer.py index 1f2974f4..f5abc7b9 100644 --- a/src/aipass/memory/apps/handlers/central_writer.py +++ b/src/aipass/memory/apps/handlers/central_writer.py @@ -36,7 +36,18 @@ logger = get_system_logger() # ============================================================================= _MEMORY_ROOT = Path(__file__).resolve().parents[3] -CENTRAL_FILE = _MEMORY_ROOT / "central" / "memory_bank.central.json" + + +def _find_repo_root() -> Path: + """Walk up from this file to find repo root (contains AIPASS_REGISTRY.json).""" + current = Path(__file__).resolve().parent + for parent in [current] + list(current.parents): + if (parent / "AIPASS_REGISTRY.json").exists(): + return parent + return Path.cwd() + + +CENTRAL_FILE = _find_repo_root() / ".ai_central" / "MEMORY.central.json" CHROMA_DB_PATH = _MEMORY_ROOT / ".chroma" ARCHIVE_DIR = _MEMORY_ROOT / ".archive" diff --git a/src/aipass/memory/apps/handlers/dashboard_push.py b/src/aipass/memory/apps/handlers/dashboard_push.py index 22d9881d..84e415fc 100644 --- a/src/aipass/memory/apps/handlers/dashboard_push.py +++ b/src/aipass/memory/apps/handlers/dashboard_push.py @@ -38,7 +38,6 @@ _MEMORY_ROOT = Path(__file__).resolve().parents[3] # CONSTANTS # ============================================================================= -CENTRAL_FILE = _MEMORY_ROOT / "central" / "memory_bank.central.json" CONFIG_PATH = _MEMORY_ROOT / "config" / "memory_bank.config.json" TEMPLATE_VERSION_FILE = _MEMORY_ROOT / "templates" / ".template_version.json" @@ -52,6 +51,7 @@ def _find_repo_root() -> Path: return Path.cwd() +CENTRAL_FILE = _find_repo_root() / ".ai_central" / "MEMORY.central.json" AIPASS_REGISTRY = _find_repo_root() / "AIPASS_REGISTRY.json" # Near-rollover threshold: branches with fewer than this many lines remaining diff --git a/src/aipass/memory/apps/handlers/storage/chroma_subprocess.py b/src/aipass/memory/apps/handlers/storage/chroma_subprocess.py index 45eecc4e..03cd3237 100755 --- a/src/aipass/memory/apps/handlers/storage/chroma_subprocess.py +++ b/src/aipass/memory/apps/handlers/storage/chroma_subprocess.py @@ -107,6 +107,49 @@ def _list_collections(db_path=None): } +def _check_plan(plan_label, db_path=None): + """Check if a plan has been vectorized in ChromaDB. + + Args: + plan_label: Plan label to search for (e.g., "FPLAN-0126") + db_path: Optional path to Chroma database + + Returns: + Dict with found status, count of matching chunks, and source files + """ + client = _get_client(db_path) + + collection_name = "flow_flow_plans" + try: + collection = client.get_collection(collection_name, embedding_function=None) + except Exception: + return { + 'success': True, + 'found': False, + 'count': 0, + 'source_files': [], + 'message': f'Collection {collection_name} does not exist' + } + + result = collection.get(include=["metadatas"]) + metadatas = result.get('metadatas', []) + + matching_files = set() + match_count = 0 + for metadata in metadatas: + source_file = metadata.get('source_file', '') + if plan_label in source_file: + match_count += 1 + matching_files.add(source_file) + + return { + 'success': True, + 'found': match_count > 0, + 'count': match_count, + 'source_files': sorted(matching_files) + } + + def _search_vectors(query_embedding, branch=None, memory_type=None, n_results=5, db_path=None): """Search for similar vectors.""" client = _get_client(db_path) @@ -186,6 +229,11 @@ def main(): n_results=input_data.get('n_results', 5), db_path=input_data.get('db_path') ) + elif operation == 'check_plan': + result = _check_plan( + plan_label=input_data.get('plan_label'), + db_path=input_data.get('db_path') + ) else: result = {'success': False, 'error': f'Unknown operation: {operation}'} diff --git a/src/aipass/memory/apps/modules/verify.py b/src/aipass/memory/apps/modules/verify.py new file mode 100644 index 00000000..4a4bc34a --- /dev/null +++ b/src/aipass/memory/apps/modules/verify.py @@ -0,0 +1,285 @@ +# =================== AIPass ==================== +# Name: verify.py +# Description: Plan Verification Module +# Version: 0.1.0 +# Created: 2026-03-18 +# Modified: 2026-03-18 +# ============================================= + +""" +Plan Verification Module + +Checks whether a plan has been vectorized in ChromaDB. + +Purpose: + Thin orchestration layer - calls chroma_subprocess via subprocess + to query the flow_flow_plans collection for a given plan label. +""" + +import subprocess +import json +import os +import sys +from pathlib import Path +from typing import List + +from aipass.cli.apps.modules import console, error +from aipass.memory.apps.handlers.json import json_handler + +# ============================================================================= +# INFRASTRUCTURE SETUP +# ============================================================================= + +# Subprocess script for ChromaDB operations (run in memory venv) +_HANDLERS_DIR = Path(__file__).resolve().parent.parent / "handlers" +CHROMA_SUBPROCESS_SCRIPT = _HANDLERS_DIR / "storage" / "chroma_subprocess.py" + +# Memory venv python -- auto-detect from memory/.venv/ or use env var override +_MEMORY_ROOT = Path(__file__).resolve().parents[2] +_MEMORY_VENV_PYTHON = _MEMORY_ROOT / ".venv" / "bin" / "python" + + +def _get_memory_python() -> str: + """Get the Python executable for memory ML operations.""" + env_override = os.environ.get("AIPASS_MEMORY_PYTHON") + if env_override: + return env_override + if _MEMORY_VENV_PYTHON.exists(): + return str(_MEMORY_VENV_PYTHON) + return sys.executable + + +# ============================================================================= +# COMMAND HANDLERS +# ============================================================================= + +def handle_command(command: str, args: List[str]) -> bool: + """ + Handle verify commands with seedgo-compliant introspection. + + Routing: + verify (no args) -> print_introspection() + verify --help/-h/help -> print_help() + verify -> check plan vectorization + + Args: + command: Command name + args: Additional arguments (plan label) + + Returns: + True if command handled, False otherwise + """ + # Top-level help (backward compat -- entry point may send these) + if command in ('--help', '-h', 'help'): + print_help() + return True + + if command == 'verify': + # No args -> introspection (seedgo standard) + if not args: + print_introspection() + return True + + # --help / -h / help -> full help + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + + # First arg is the plan label + plan_label = args[0] + _verify_plan(plan_label) + return True + + return False + + +# ============================================================================= +# VERIFICATION LOGIC +# ============================================================================= + +def _check_plan_subprocess(plan_label: str) -> dict: + """ + Check plan vectorization via chroma_subprocess. + + Args: + plan_label: Plan label to check (e.g., "FPLAN-0126") + + Returns: + Dict with success, found, count, source_files + """ + python_path = _get_memory_python() + input_data = json.dumps({ + 'operation': 'check_plan', + 'plan_label': plan_label, + }) + + try: + result = subprocess.run( + [python_path, str(CHROMA_SUBPROCESS_SCRIPT)], + input=input_data, + capture_output=True, + text=True, + timeout=60 + ) + + if result.returncode != 0: + return {'success': False, 'error': result.stderr or 'Subprocess failed'} + + return json.loads(result.stdout) + except subprocess.TimeoutExpired: + return {'success': False, 'error': 'Check operation timed out'} + except json.JSONDecodeError as e: + return {'success': False, 'error': f'Invalid JSON response: {e}'} + except Exception as e: + return {'success': False, 'error': str(e)} + + +def is_plan_vectorized(plan_label: str) -> dict: + """ + Check if a plan has been vectorized in ChromaDB. + + Programmatic API for use by other modules. + + Args: + plan_label: Plan label to check (e.g., "FPLAN-0126") + + Returns: + Dict with keys: success, found, count, source_files + """ + return _check_plan_subprocess(plan_label) + + +def _verify_plan(plan_label: str) -> None: + """ + Verify a plan's vectorization status and display the result. + + Args: + plan_label: Plan label to check (e.g., "FPLAN-0126") + """ + result = _check_plan_subprocess(plan_label) + + if not result.get('success'): + error(result.get('error', 'Unknown error')) + json_handler.log_operation("verify_plan", {"plan_label": plan_label, "success": False}) + return + + found = result.get('found', False) + count = result.get('count', 0) + + console.print() + if found: + console.print(f" Plan {plan_label}: [green]Vectorized[/green] ({count} chunks)") + else: + console.print(f" Plan {plan_label}: [red]NOT vectorized[/red]") + console.print() + + json_handler.log_operation("verify_plan", { + "plan_label": plan_label, + "found": found, + "count": count, + "success": True, + }) + + +# ============================================================================= +# INTROSPECTION +# ============================================================================= + +def _discover_handlers() -> dict[str, list[str]]: + """Auto-discover handler directories and their Python files. + + Scans the handlers/ directory relative to this module. + + Returns: + Dict mapping handler directory name to list of .py filenames + (excluding __init__.py and __pycache__). + """ + handlers_dir = Path(__file__).resolve().parent.parent / "handlers" + result: dict[str, list[str]] = {} + if not handlers_dir.exists(): + return result + for d in sorted(handlers_dir.iterdir()): + if not d.is_dir() or d.name.startswith("__"): + continue + py_files = sorted( + f.name for f in d.iterdir() + if f.is_file() and f.suffix == ".py" and f.name != "__init__.py" + ) + if py_files: + result[d.name] = py_files + return result + + +def print_introspection() -> None: + """Display module introspection info (seedgo standard). + + Called when 'verify' is invoked with no arguments. + Shows module identity, connected handlers, and next-step hints. + """ + console.print() + console.print("[bold cyan]verify Module[/bold cyan]") + console.print("Checks whether a plan has been vectorized in ChromaDB") + console.print() + + # Connected handlers (auto-discovered) + handlers = _discover_handlers() + console.print("[yellow]Connected Handlers:[/yellow]") + if handlers: + for dir_name, files in handlers.items(): + file_list = ", ".join(files) + console.print(f" [cyan]handlers/{dir_name}/[/cyan] [dim]{file_list}[/dim]") + else: + console.print(" [dim]No handlers found[/dim]") + console.print() + + # Next-step hints + console.print("[yellow]Next:[/yellow]") + console.print(' [green]drone @memory verify FPLAN-0126[/green] [dim]# Check if plan is vectorized[/dim]') + console.print(" [green]drone @memory verify --help[/green] [dim]# Full usage guide[/dim]") + console.print() + + +def print_help() -> None: + """Display verify module help.""" + console.print() + console.print("[bold cyan]Verify Module - Plan Vectorization Check[/bold cyan]") + console.print() + console.print("[bold]USAGE:[/bold]") + console.print(" drone @memory verify ") + console.print() + console.print("[bold]COMMANDS:[/bold]") + console.print(" [cyan]verify [/cyan] Check if a plan is vectorized in ChromaDB") + console.print(" [cyan]help[/cyan] Show this help message") + console.print() + console.print("[bold]EXAMPLES:[/bold]") + console.print(" [dim]drone @memory verify FPLAN-0126[/dim]") + console.print(" [dim]drone @memory verify HPLAN-0001[/dim]") + console.print() + console.print("[bold]HOW IT WORKS:[/bold]") + console.print(" 1. Query the flow_flow_plans ChromaDB collection") + console.print(" 2. Filter entries by source_file metadata matching the plan label") + console.print(" 3. Report vectorization status and chunk count") + console.print() + + +# ============================================================================= +# STANDALONE EXECUTION +# ============================================================================= + +if __name__ == "__main__": + # No args -> introspection (seedgo standard) + if len(sys.argv) < 2: + handle_command('verify', []) + sys.exit(0) + + # --help -> full help + if sys.argv[1] in ('--help', '-h', 'help'): + handle_command('verify', ['--help']) + sys.exit(0) + + # Execute command via handle_command + command = sys.argv[1] + if not handle_command(command, sys.argv[2:]): + console.print(f"[red]Unknown command:[/red] {command}") + console.print("Run with [cyan]help[/cyan] for available commands") + sys.exit(1) diff --git a/src/aipass/memory/config/memory_bank.config.json b/src/aipass/memory/config/memory_bank.config.json index daeed13e..051476ed 100644 --- a/src/aipass/memory/config/memory_bank.config.json +++ b/src/aipass/memory/config/memory_bank.config.json @@ -16,7 +16,7 @@ }, "plans": { "enabled": true, - "path": "src/aipass/flow/processed_plans", + "path": "src/aipass/flow/backup/processed_plans", "supported_extensions": [".md"], "collection_name": "flow_plans" }