diff --git a/src/aipass/flow/HPLAN-0001_design_topic_2026-03-18.md b/src/aipass/flow/HPLAN-0001_design_topic_2026-03-18.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/aipass/flow/PPLAN-0002_design_topic_2026-03-18.md b/src/aipass/flow/PPLAN-0002_design_topic_2026-03-18.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/aipass/flow/apps/handlers/mbank/process.py b/src/aipass/flow/apps/handlers/mbank/process.py index 362b9b75..7927dc62 100644 --- a/src/aipass/flow/apps/handlers/mbank/process.py +++ b/src/aipass/flow/apps/handlers/mbank/process.py @@ -29,6 +29,8 @@ from datetime import datetime, timezone from typing import Dict, List, Optional, Any from aipass.flow.apps.handlers.json import json_handler +from aipass.prax.apps.modules.logger import system_logger as logger +from aipass.cli.apps.modules import error as cli_error, warning as cli_warning # AI summarization removed — OpenRouter API no longer needed here # from aipass.api.apps.modules.openrouter_client import get_response @@ -689,7 +691,10 @@ def verify_and_heal_orphaned_plans() -> Dict[str, Any]: except Exception: continue for plan_num, plan_info in registry.get("plans", {}).items(): - if plan_info.get("processed") is True and plan_info.get("cleanup_completed") is True: + # Heal ANY closed plan whose file still sits at its original location. + # Covers both fully-processed plans and auto-closed orphans that + # never entered the post-close pipeline. + if plan_info.get("status") == "closed": original_path = Path(plan_info.get("file_path", "")) if original_path.exists(): orphans_found += 1 @@ -757,12 +762,17 @@ def process_closed_plans() -> Dict[str, Any]: if archive_success: processed_count += 1 - # Best-effort vector processing + # Vector processing — errors go to prax log + console try: from aipass.memory.apps.handlers.intake.plans_processor import process_plans # type: ignore[import-not-found] process_plans() - except Exception: - pass + logger.info("[mbank] Vector intake completed for %s", plan_label) + except ImportError: + logger.error("[mbank] Vector intake FAILED for %s — plans_processor not found", plan_label) + cli_error(f"Vector intake unavailable — memory plans_processor not found ({plan_label})") + except Exception as vec_err: + logger.error("[mbank] Vector intake FAILED for %s: %s", plan_label, vec_err) + cli_error(f"Vector intake failed for {plan_label}: {vec_err}") results.append({"plan": plan_label, "status": "archived", "correlation_id": correlation_id}) else: error_count += 1 diff --git a/src/aipass/flow/apps/handlers/plan/close_ops.py b/src/aipass/flow/apps/handlers/plan/close_ops.py index 6d3f7753..20dcfee4 100644 --- a/src/aipass/flow/apps/handlers/plan/close_ops.py +++ b/src/aipass/flow/apps/handlers/plan/close_ops.py @@ -108,6 +108,7 @@ def _spawn_background_runner(): def close_plan_impl(plan_num: Any = None, confirm: bool = False, all_plans: bool = False, spawn_background: bool = True, + dry_run: bool = False, # Dependencies injected from module normalize_plan_number: Any = None, load_registry: Any = None, @@ -131,6 +132,7 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False, all_plans: If True, close all open plans (default False) spawn_background: Whether to spawn background post-processing (default True). Set False when called from close_all_plans() to avoid race condition. + dry_run: If True, preview what would be closed without taking action (default False) (remaining args): Handler/service dependencies injected by module Returns: @@ -141,7 +143,7 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False, # Handle --all flag if all_plans: - return close_all_plans_fn(confirm) + return close_all_plans_fn(confirm, dry_run=dry_run) # Single plan closure if not plan_num: @@ -194,6 +196,24 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False, # Extract prefix for display functions (e.g. "FPLAN", "DPLAN") plan_prefix = _extract_prefix(plan_label) or "FPLAN" + # DRY RUN: Preview what would be closed, then return early + if dry_run: + location = plan_info.get("location", "unknown") + subject = plan_info.get("subject", "No subject") + status = plan_info.get("status", "unknown") + messages.append({"type": "dim", "text": f"[DRY RUN] Would close {plan_label}"}) + messages.append({"type": "dim", "text": f" Location: {location}"}) + messages.append({"type": "dim", "text": f" Subject: {subject}"}) + messages.append({"type": "dim", "text": f" Status: {status}"}) + messages.append({"type": "dim", "text": "No action taken."}) + logger.info(f"[{MODULE_NAME}] Dry run: would close {plan_label}") + return { + "success": True, + "messages": messages, + "plan_key": plan_key, + "cancelled": False, + } + # 4. IDEMPOTENCY CHECK: Prevent double-closing (with orphan cleanup) if plan_info['status'] == 'closed': closed_date = plan_info.get('closed', 'unknown') @@ -300,21 +320,57 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False, "cancelled": False, } - # --- Step 3/5: Background processing --- - if spawn_background: - messages.append({"type": "step", "text": "[3/5] Starting background processing..."}) - try: - _spawn_background_runner() - logger.info(f"[{MODULE_NAME}] Spawned background post-processing for {plan_label}") - messages.append({"type": "dim", "text": " Summary generation and archival running in background"}) - except FileNotFoundError as e: - logger.warning(f"[{MODULE_NAME}] Background runner not found: {e}") - messages.append({"type": "warning", "text": " Background runner not found - will retry on next close"}) - except Exception as e: - logger.warning(f"[{MODULE_NAME}] Failed to spawn background post-processing: {e}") - messages.append({"type": "warning", "text": " Background archival failed to start - will retry on next close"}) - else: - messages.append({"type": "step", "text": "[3/5] Background processing deferred (batch mode)"}) + # --- Step 3/5: Archive plan to processed_plans --- + messages.append({"type": "step", "text": "[3/5] Archiving plan..."}) + try: + from aipass.flow.apps.handlers.mbank.process import archive_plan + archive_success = archive_plan(plan_file) + if archive_success: + # Set flags on same registry object we already have in memory + plan_info["processed"] = True + plan_info["processed_date"] = datetime.now(timezone.utc).isoformat() + plan_info["cleanup_completed"] = True + plan_info["cleanup_date"] = datetime.now(timezone.utc).isoformat() + if reg_file: + save_registry(registry, registry_file=reg_file) + else: + save_registry(registry) + logger.info(f"[{MODULE_NAME}] Archived {plan_label} to processed_plans") + messages.append({"type": "dim", "text": " Plan archived to processed_plans/"}) + else: + logger.error(f"[{MODULE_NAME}] Failed to archive {plan_label}") + messages.append({"type": "warning", "text": " Archive failed — plan file not moved"}) + except Exception as e: + logger.error(f"[{MODULE_NAME}] Archive error for {plan_label}: {e}") + messages.append({"type": "warning", "text": f" Archive error: {e}"}) + + # --- Vector intake + verification --- + # Trigger memory's plan processor via drone (no cross-branch imports) + try: + subprocess.run( + ["drone", "@memory", "process-plans"], + capture_output=True, timeout=30, + ) + except Exception: + pass # Best effort — verification below reports actual status + + # Verify vectorization via memory's verify module + try: + from aipass.memory.apps.modules.verify import is_plan_vectorized # type: ignore[import-not-found] + result = is_plan_vectorized(plan_label) + if result.get("found"): + chunk_count = result.get("count", 0) + logger.info(f"[{MODULE_NAME}] Vectorized: {plan_label} ({chunk_count} chunks)") + messages.append({"type": "dim", "text": f" Vectorized: {chunk_count} chunks in chroma"}) + else: + logger.warning(f"[{MODULE_NAME}] NOT vectorized: {plan_label}") + messages.append({"type": "warning", "text": " NOT vectorized — check drone @memory process-plans"}) + except ImportError: + logger.warning(f"[{MODULE_NAME}] Vector verify unavailable — memory verify module not found") + messages.append({"type": "warning", "text": " Vector status: unknown (memory verify not available)"}) + except Exception as vec_err: + logger.warning(f"[{MODULE_NAME}] Vector verify failed: {vec_err}") + messages.append({"type": "warning", "text": f" Vector status: unknown ({vec_err})"}) # --- Step 4/5: Update dashboards --- messages.append({"type": "step", "text": "[4/5] Updating dashboards..."}) @@ -385,7 +441,7 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False, } -def close_all_plans_impl(confirm: bool = False, +def close_all_plans_impl(confirm: bool = False, dry_run: bool = False, # Dependencies injected from module get_open_plans: Any = None, close_plan_fn: Any = None) -> Dict[str, Any]: @@ -394,6 +450,7 @@ def close_all_plans_impl(confirm: bool = False, Args: confirm: Whether to ask for bulk confirmation (default False, auto-confirms) + dry_run: If True, preview what would be closed without taking action (default False) get_open_plans: Handler function to get open plans close_plan_fn: Function to close a single plan (the module's close_plan) @@ -416,6 +473,28 @@ def close_all_plans_impl(confirm: bool = False, "total": 0, } + # DRY RUN: Preview all plans that would be closed, then return early + if dry_run: + messages.append({"type": "dim", "text": f"[DRY RUN] Would close {len(open_plans)} plan(s):"}) + for plan_num, plan_info in open_plans: + subject = plan_info.get("subject", "No subject") + location = plan_info.get("location", "unknown") + # Derive prefix from file_path if available + plan_file = Path(plan_info.get("file_path", "")) + plan_label = plan_file.stem if plan_file.name else f"PLAN-{plan_num}" + prefix = _extract_prefix(plan_label) or "FPLAN" + display_id = f"{prefix}-{plan_num}" + messages.append({"type": "dim", "text": f" {display_id:<14}{location:<14}{subject}"}) + messages.append({"type": "dim", "text": "No action taken."}) + logger.info(f"[{MODULE_NAME}] Dry run: would close {len(open_plans)} plan(s)") + return { + "success": True, + "messages": messages, + "success_count": 0, + "failure_count": 0, + "total": len(open_plans), + } + # Build plan list for display plan_list = [] for plan_num, plan_info in open_plans: diff --git a/src/aipass/flow/apps/handlers/plan/command_parser.py b/src/aipass/flow/apps/handlers/plan/command_parser.py index fc2c20e2..8d499c40 100644 --- a/src/aipass/flow/apps/handlers/plan/command_parser.py +++ b/src/aipass/flow/apps/handlers/plan/command_parser.py @@ -99,45 +99,53 @@ def parse_delete_command_args(args: List[str]) -> Tuple[str | None, bool, str | return plan_num, confirm, None -def parse_close_command_args(args: List[str]) -> Tuple[str | None, bool, bool, str | None]: +def parse_close_command_args(args: List[str]) -> Tuple[str | None, bool, bool, bool, str | None]: """ Parse arguments for close command Auto-confirms by default (running 'close' IS the intent). Use --confirm or --interactive to explicitly request a confirmation prompt. --yes/-y kept for backwards compatibility (now redundant, already auto-confirms). + --dry-run or --preview previews what would be closed without taking action. Args: args: Command arguments Returns: - Tuple of (plan_num, confirm, all_plans, error_message) + Tuple of (plan_num, confirm, all_plans, dry_run, error_message) - plan_num: Plan number from first arg, or None if --all or missing - confirm: True only if --confirm or --interactive flag present, False otherwise - all_plans: True if --all flag present, False otherwise + - dry_run: True if --dry-run or --preview flag present, False otherwise - error_message: None if valid, error string if invalid args Examples: >>> parse_close_command_args(["42"]) - ("42", False, False, None) + ("42", False, False, False, None) >>> parse_close_command_args(["42", "--yes"]) - ("42", False, False, None) + ("42", False, False, False, None) >>> parse_close_command_args(["42", "--confirm"]) - ("42", True, False, None) + ("42", True, False, False, None) >>> parse_close_command_args(["42", "--interactive"]) - ("42", True, False, None) + ("42", True, False, False, None) >>> parse_close_command_args(["--all"]) - (None, False, True, None) + (None, False, True, False, None) >>> parse_close_command_args(["--all", "--confirm"]) - (None, True, True, None) + (None, True, True, False, None) + + >>> parse_close_command_args(["42", "--dry-run"]) + ("42", False, False, True, None) + + >>> parse_close_command_args(["--all", "--preview"]) + (None, False, True, True, None) >>> parse_close_command_args([]) - (None, False, False, "Plan number or --all required") + (None, False, False, False, "Plan number or --all required") """ # Check for --all flag all_plans = '--all' in args @@ -147,18 +155,21 @@ def parse_close_command_args(args: List[str]) -> Tuple[str | None, bool, bool, s # --yes/-y kept for backwards compat (redundant, already auto-confirms) confirm = '--confirm' in args or '--interactive' in args + # Check for --dry-run or --preview flag + dry_run = '--dry-run' in args or '--preview' in args + # If --all, plan_num is None if all_plans: - return None, confirm, True, None + return None, confirm, True, dry_run, None # Otherwise, need plan number # Filter out flag args to find the plan number non_flag_args = [a for a in args if not a.startswith('--') and a not in ('-y',)] if not non_flag_args: - return None, False, False, "Plan number or --all required" + return None, False, False, dry_run, "Plan number or --all required" plan_num = non_flag_args[0] - return plan_num, confirm, False, None + return plan_num, confirm, False, dry_run, None def parse_restore_command_args(args: List[str]) -> Tuple[str | None, str | None]: diff --git a/src/aipass/flow/apps/handlers/template/registry_ops.py b/src/aipass/flow/apps/handlers/template/registry_ops.py index 4f076811..e4084906 100644 --- a/src/aipass/flow/apps/handlers/template/registry_ops.py +++ b/src/aipass/flow/apps/handlers/template/registry_ops.py @@ -147,6 +147,38 @@ def load_registry() -> Dict[str, Any]: "type_count": len(data["types"]), } + # Auto-heal: prune orphaned types (directory deleted but registry entry remains) + templates_dir = FLOW_ROOT / "templates" + orphaned = [ + dir_name + for dir_name in data["types"] + if dir_name not in _PROTECTED_TYPES + and not (templates_dir / dir_name).is_dir() + ] + if orphaned: + plan_registry_dir = FLOW_ROOT / "flow_json" + for dir_name in orphaned: + entry = data["types"][dir_name] + shorthand = entry.get("shorthand", entry.get("prefix", "").lower()) + logger.info( + "[%s] Auto-pruning orphaned type '%s' (directory missing)", + MODULE_NAME, + dir_name, + ) + del data["types"][dir_name] + + # Clean up the per-type plan registry JSON + if shorthand: + plan_reg = plan_registry_dir / f"{shorthand}_registry.json" + if plan_reg.exists(): + plan_reg.unlink() + logger.info( + "[%s] Removed orphaned plan registry: %s", + MODULE_NAME, + plan_reg.name, + ) + save_registry(data) + return data diff --git a/src/aipass/flow/apps/modules/close_plan.py b/src/aipass/flow/apps/modules/close_plan.py index 4b632d82..6a85b883 100644 --- a/src/aipass/flow/apps/modules/close_plan.py +++ b/src/aipass/flow/apps/modules/close_plan.py @@ -185,11 +185,13 @@ def print_help(): console.print("[yellow]OPTIONS:[/yellow]") console.print(" --all Close all open plans") console.print(" --confirm Interactive confirmation prompt") + console.print(" --dry-run Preview what would be closed (no action taken)") console.print() console.print("[yellow]EXAMPLES:[/yellow]") console.print(" [dim]drone @flow close FPLAN-0042[/dim] # Close specific plan") console.print(" [dim]drone @flow close DPLAN-0005[/dim] # Close a DPLAN") console.print(" [dim]drone @flow close --all[/dim] # Close all open plans") + console.print(" [dim]drone @flow close --all --dry-run[/dim] # Preview close-all") console.print() @@ -197,7 +199,7 @@ def print_help(): # CLOSE PLAN WORKFLOW (thin orchestrator) # ============================================= -def close_plan(plan_num: str | None = None, confirm: bool = False, all_plans: bool = False, spawn_background: bool = True) -> bool: +def close_plan(plan_num: str | None = None, confirm: bool = False, all_plans: bool = False, spawn_background: bool = True, dry_run: bool = False) -> bool: """ Orchestrate plan closure workflow (thin orchestrator) @@ -218,6 +220,7 @@ def close_plan(plan_num: str | None = None, confirm: bool = False, all_plans: bo all_plans: If True, close all open plans (default False) spawn_background: Whether to spawn background post-processing (default True). Set False when called from close_all_plans() to avoid race condition. + dry_run: If True, preview what would be closed without taking action (default False) Returns: True if successful, False otherwise @@ -227,6 +230,7 @@ def close_plan(plan_num: str | None = None, confirm: bool = False, all_plans: bo confirm=confirm, all_plans=all_plans, spawn_background=spawn_background, + dry_run=dry_run, # Inject dependencies normalize_plan_number=normalize_plan_number, load_registry=load_registry, @@ -249,18 +253,20 @@ def close_plan(plan_num: str | None = None, confirm: bool = False, all_plans: bo return bool(result) -def close_all_plans(confirm: bool = False) -> bool: +def close_all_plans(confirm: bool = False, dry_run: bool = False) -> bool: """ Close all open plans in one operation (thin orchestrator) Args: confirm: Whether to ask for bulk confirmation (default False, auto-confirms) + dry_run: If True, preview what would be closed without taking action (default False) Returns: True if at least one plan closed successfully, False otherwise """ result = close_all_plans_impl( confirm=confirm, + dry_run=dry_run, get_open_plans=get_open_plans, close_plan_fn=close_plan, ) @@ -313,7 +319,7 @@ def handle_command(command: str, args: List[str]) -> bool: ) # 1. PARSE ARGS: Use command_parser handler - plan_num, confirm, all_plans, error = parse_close_command_args(args) + plan_num, confirm, all_plans, dry_run, error = parse_close_command_args(args) # 2. VALIDATE: Check for parsing errors if error: @@ -321,7 +327,7 @@ def handle_command(command: str, args: List[str]) -> bool: return True # Command was handled (error already displayed) # 3. EXECUTE: Run workflow orchestrator - close_plan(plan_num=plan_num, confirm=confirm, all_plans=all_plans) + close_plan(plan_num=plan_num, confirm=confirm, all_plans=all_plans, dry_run=dry_run) # 4. RETURN: True = command was handled (even if the operation failed, # the error has already been displayed -- returning False would cause diff --git a/src/aipass/flow/templates/hhhhh/zzzzz.md b/src/aipass/flow/templates/hhhhh/zzzzz.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/aipass/flow/templates/patrick/patrick.md b/src/aipass/flow/templates/patrick/patrick.md deleted file mode 100644 index e69de29b..00000000 diff --git a/src/aipass/flow/templates/testing/default.md b/src/aipass/flow/templates/testing/default.md deleted file mode 100644 index 5afb7a0f..00000000 --- a/src/aipass/flow/templates/testing/default.md +++ /dev/null @@ -1,13 +0,0 @@ -# {plan_number}: {subject} - -Tag: {tag} - -> Testing template — auto-discovered from filesystem - ---- - -## Notes - ---- -*Created: {today}* -*Updated: {today}* diff --git a/src/aipass/flow/templates/testing/test.md b/src/aipass/flow/templates/testing/test.md deleted file mode 100644 index e69de29b..00000000 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/intake/plans_processor.py b/src/aipass/memory/apps/handlers/intake/plans_processor.py index 84ffa05e..9bc19d25 100644 --- a/src/aipass/memory/apps/handlers/intake/plans_processor.py +++ b/src/aipass/memory/apps/handlers/intake/plans_processor.py @@ -259,10 +259,16 @@ def process_plans() -> Dict[str, Any]: logger.info(f"[plans] Found {len(unprocessed)} unprocessed plan files") - total_chunks = 0 - files_processed = 0 errors = [] + # -- Phase 1: Read all files, chunk them, collect texts + metadatas ---------- + all_texts: List[str] = [] + all_metadatas: List[Dict[str, str]] = [] + # Track which files produced chunks (for manifest update) + files_with_chunks: List[Path] = [] + # Files with 0 chunks still get marked in manifest (e.g. template content) + files_without_chunks: List[Path] = [] + for plan_file in unprocessed: try: text = plan_file.read_text(encoding='utf-8') @@ -270,58 +276,65 @@ def process_plans() -> Dict[str, Any]: errors.append(f'{plan_file.name}: read error: {e}') continue - # Chunk the plan chunks = _chunk_plan_text(text, plan_file.name) if not chunks: - manifest[plan_file.name] = datetime.now().isoformat() + files_without_chunks.append(plan_file) continue - # Extract texts and build metadata - texts = [c['text'] for c in chunks] - metadatas = [ - { + files_with_chunks.append(plan_file) + for c in chunks: + all_texts.append(c['text']) + all_metadatas.append({ 'source_file': plan_file.name, 'section': c['section'], 'processed_at': datetime.now().isoformat(), 'type': 'plan' - } - for c in chunks - ] + }) - # Embed - embed_result = _embed_texts(texts) - if not embed_result.get('success'): - errors.append(f"{plan_file.name}: embed error: {embed_result.get('error')}") - continue + total_chunks = len(all_texts) + files_processed = 0 - embeddings = embed_result.get('embeddings', []) - if not embeddings: - errors.append(f'{plan_file.name}: no embeddings returned') - continue - - # Store - store_result = _store_vectors(embeddings, texts, metadatas, collection_name) - if not store_result.get('success'): - errors.append(f"{plan_file.name}: store error: {store_result.get('error')}") - continue - - # Mark as processed + # Mark empty-chunk files in manifest immediately (nothing to embed) + for plan_file in files_without_chunks: manifest[plan_file.name] = datetime.now().isoformat() - files_processed += 1 - total_chunks += len(chunks) - logger.info(f"[plans] Processed {plan_file.name}: {len(chunks)} chunks vectorized") - # Save manifest + # -- Phase 2: Batch embed + store (single subprocess each) ------------------ + if all_texts: + logger.info(f"[plans] Batch embedding {total_chunks} chunks from {len(files_with_chunks)} files") + + embed_result = _embed_texts(all_texts) + if not embed_result.get('success'): + error_msg = f"batch embed error: {embed_result.get('error')}" + logger.error(f"[plans] {error_msg}") + errors.append(error_msg) + else: + embeddings = embed_result.get('embeddings', []) + if not embeddings: + errors.append('batch embed returned no embeddings') + else: + store_result = _store_vectors(embeddings, all_texts, all_metadatas, collection_name) + if not store_result.get('success'): + error_msg = f"batch store error: {store_result.get('error')}" + logger.error(f"[plans] {error_msg}") + errors.append(error_msg) + else: + # Success — mark all chunk-producing files in manifest + for plan_file in files_with_chunks: + manifest[plan_file.name] = datetime.now().isoformat() + files_processed = len(files_with_chunks) + logger.info(f"[plans] Batch complete: {files_processed} files, {total_chunks} chunks vectorized") + + # Save manifest (includes empty-chunk files even if embedding failed) _save_manifest(manifest) - result = { - 'success': files_processed > 0 or not errors, + result: Dict[str, Any] = { + 'success': files_processed > 0 or (not errors and not files_with_chunks), 'files_processed': files_processed, - 'total_chunks': total_chunks, + 'total_chunks': total_chunks if files_processed > 0 else 0, } if errors: result['errors'] = errors - json_handler.log_operation("process_plans", {"files_processed": files_processed, "total_chunks": total_chunks, "success": result['success']}) + json_handler.log_operation("process_plans", {"files_processed": files_processed, "total_chunks": result['total_chunks'], "success": result['success']}) return result diff --git a/src/aipass/memory/apps/handlers/monitor/memory_watcher.py b/src/aipass/memory/apps/handlers/monitor/memory_watcher.py index ffe467d0..1b1672a3 100644 --- a/src/aipass/memory/apps/handlers/monitor/memory_watcher.py +++ b/src/aipass/memory/apps/handlers/monitor/memory_watcher.py @@ -295,12 +295,14 @@ def _check_memory_pool() -> Dict[str, Any]: def _check_plans() -> Dict[str, Any]: """ - Check plans directory for files to vectorize. + Check plans directory for unprocessed files (count only). - Processes any plan files that haven't been vectorized yet. + Does NOT call process_plans() — that spawns heavy ML subprocesses. + Only counts pending files and reports. Use 'drone @memory process-plans' + to trigger actual vectorization. Returns: - Dict with processing status + Dict with pending file count """ import json @@ -308,7 +310,7 @@ def _check_plans() -> Dict[str, Any]: # Load config try: - with open(config_path) as f: + with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) plans_config = config.get('plans', {}) except Exception: @@ -320,7 +322,8 @@ def _check_plans() -> Dict[str, Any]: # Get plans path and count files (supports absolute paths) plans_dir = plans_config.get('path', 'plans') - plans_path = Path(plans_dir) if Path(plans_dir).is_absolute() else _MEMORY_ROOT / plans_dir + repo_root = _find_repo_root() + plans_path = Path(plans_dir) if Path(plans_dir).is_absolute() else repo_root / plans_dir extensions = plans_config.get('supported_extensions', ['.md']) if not plans_path.exists(): @@ -333,21 +336,24 @@ def _check_plans() -> Dict[str, Any]: file_count = len(files) if file_count == 0: - return {'success': True, 'files_in_plans': 0, 'action': 'none'} + return {'success': True, 'pending_files': 0, 'action': 'count_only'} - # Process plans to vectors - try: - # NOTE: intake module not yet ported to aipass.memory package - from aipass.memory.apps.handlers.intake.plans_processor import process_plans # type: ignore[import-not-found] - result = process_plans() - return { - 'success': result.get('success', False), - 'files_processed': result.get('files_processed', 0), - 'total_chunks': result.get('total_chunks', 0), - 'action': 'processed' - } - except Exception as e: - return {'success': False, 'error': str(e), 'action': 'failed'} + # Load manifest to count unprocessed files + manifest_path = _MEMORY_ROOT / "config" / ".plans_processed.json" + manifest: Dict[str, str] = {} + if manifest_path.exists(): + try: + manifest = json.loads(manifest_path.read_text(encoding='utf-8')) + except Exception: + pass + + pending = [f for f in files if f.name not in manifest] + pending_count = len(pending) + + if pending_count > 0: + logger.info(f"[plans] {pending_count} plans pending vectorization. Run: drone @memory process-plans") + + return {'success': True, 'pending_files': pending_count, 'action': 'count_only'} def _check_code_archive() -> Dict[str, Any]: 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/rollover.py b/src/aipass/memory/apps/modules/rollover.py index fefb2945..cf119305 100755 --- a/src/aipass/memory/apps/modules/rollover.py +++ b/src/aipass/memory/apps/modules/rollover.py @@ -133,6 +133,10 @@ def handle_command(command: str, args: List[str]) -> bool: sync_line_counts() return True + elif command == 'process-plans': + process_plans_command() + return True + return False @@ -226,6 +230,61 @@ def run_rollover() -> bool: return success_count > 0 +# ============================================================================= +# PLAN VECTORIZATION +# ============================================================================= + +def process_plans_command() -> None: + """ + Process pending plan files into vector storage. + + Batches all chunks from all files into a single embed + store call. + """ + console.print() + console.print(Panel.fit( + "[bold cyan]Memory - Process Plans[/bold cyan]", + border_style="cyan", + box=box.ROUNDED + )) + console.print() + + console.print("[cyan]Processing plan files into vector storage...[/cyan]") + console.print() + + try: + from ..handlers.intake.plans_processor import process_plans + result = process_plans() + except Exception as e: + error(f"Plan processing failed: {e}") + return + + if not result.get('success'): + error(result.get('error', 'Unknown error')) + if result.get('errors'): + for err in result['errors']: + error(err) + return + + files_processed = result.get('files_processed', 0) + total_chunks = result.get('total_chunks', 0) + reason = result.get('reason', '') + + if files_processed == 0 and reason: + console.print(f"[green]>[/green] {reason}") + elif files_processed == 0: + console.print("[green]>[/green] No new plans to process") + else: + console.print(f"[green]>[/green] Processed {files_processed} files ({total_chunks} chunks vectorized)") + + if result.get('errors'): + console.print() + for err in result['errors']: + error(err) + + console.print() + json_handler.log_operation("process_plans_command", {"files_processed": files_processed, "total_chunks": total_chunks}) + + # ============================================================================= # LINE COUNT SYNC # ============================================================================= 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/.plans_processed.json b/src/aipass/memory/config/.plans_processed.json index d38d0e69..950720e2 100644 --- a/src/aipass/memory/config/.plans_processed.json +++ b/src/aipass/memory/config/.plans_processed.json @@ -7,5 +7,120 @@ "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" + "FPLAN-0047_memory_rollover_introspection_2026-03-15.md": "2026-03-17T17:04:54.278205", + "PPLAN-0001_test_pplan_auto_resolve_2026-03-18.md": "2026-03-18T22:54:42.823250", + "FPLAN-0130_e2e_vector_test_2026-03-18.md": "2026-03-18T22:55:51.288347", + "FPLAN-0118_vector_logging_test_2026-03-18.md": "2026-03-18T22:55:51.288363", + "FPLAN-0112_verify_fplan_2026-03-18.md": "2026-03-18T22:55:51.288366", + "FPLAN-0059_prax_introspection_gates_11_modules_2026-03-17.md": "2026-03-18T22:55:51.288369", + "FPLAN-0058_simplify_logging_execute_two_tier_refact_2026-03-16.md": "2026-03-18T22:55:51.288372", + "FPLAN-0007_seedgo_architecture_checker_activation_2026-03-07.md": "2026-03-18T22:55:51.288374", + "FPLAN-0054_symbolic_module_handlers_import_moderniz_2026-03-16.md": "2026-03-18T22:55:51.288377", + "FPLAN-0097_fix_commons_integration_gaps_fts5_sync_visito_2026-03-17.md": "2026-03-18T22:55:51.288379", + "FPLAN-0073_json_structure_standard_compliance_memor_2026-03-17.md": "2026-03-18T22:55:51.288381", + "FPLAN-0078_unified_plan_pipeline_plugin_architectur_2026-03-17.md": "2026-03-18T22:55:51.288384", + "DPLAN-0010_test_dplan_for_brainstorming_notification_sys_2026-03-17.md": "2026-03-18T22:55:51.288386", + "DPLAN-0028_verify_dplan_2026-03-18.md": "2026-03-18T22:55:51.288389", + "DPLAN-0007_final_verify_dplan_2026-03-17.md": "2026-03-18T22:55:51.288392", + "FPLAN-0128_config_fix_verify_command_for_plan_vectorizat_2026-03-18.md": "2026-03-18T22:55:51.288394", + "FPLAN-0122_pyc_bust_test_2026-03-18.md": "2026-03-18T22:55:51.288396", + "FPLAN-0065_dplan_0056_phase_2_seedgo_json_self_comp_2026-03-17.md": "2026-03-18T22:55:51.288398", + "FPLAN-0036_api_as_centralized_external_service_gate_2026-03-14.md": "2026-03-18T22:55:51.288401", + "FPLAN-0129_vector_verify_test_2026-03-18.md": "2026-03-18T22:55:51.288403", + "FPLAN-0060_prax_diagnostics_type_errors_cleanup_2026-03-17.md": "2026-03-18T22:55:51.288405", + "FPLAN-0126_foreground_archive_test_2026-03-18.md": "2026-03-18T22:55:51.288407", + "FPLAN-0124_dispatch_ux_redesign_merge_sendwake_into_sing_2026-03-18.md": "2026-03-18T22:55:51.288409", + "FPLAN-0082_close_pipeline_test_2026-03-17.md": "2026-03-18T22:55:51.288412", + "FPLAN-0069_spawn_seedgo_100_compliance_introspectio_2026-03-17.md": "2026-03-18T22:55:51.288414", + "DPLAN-0008_drone_git_module_safe_pr_workflow_with_lockfi_2026-03-17.md": "2026-03-18T22:55:51.288416", + "TPLAN-0001_test_tplan_via_registry_2026-03-18.md": "2026-03-18T22:55:51.288418", + "FPLAN-0064_daemon_branch_health_cleanup_2026-03-17.md": "2026-03-18T22:55:51.288420", + "DPLAN-0025_prax_monitor_restoration_rich_output_caller_a_2026-03-18.md": "2026-03-18T22:55:51.288423", + "FPLAN-0028_registry_isolation_phase_1_2026-03-13.md": "2026-03-18T22:55:51.288425", + "FPLAN-0026_memory_bank_full_operational_baseline_2026-03-12.md": "2026-03-18T22:55:51.288427", + "FPLAN-0008_spawn_rebuild_full_branch_lifecycle_mana_2026-03-07.md": "2026-03-18T22:55:51.288430", + "DPLAN-0027_registry_integration_test_dplan_2026-03-18.md": "2026-03-18T22:55:51.288432", + "FPLAN-0066_api_seedgo_compliance_cleanup_2026-03-17.md": "2026-03-18T22:55:51.288434", + "FPLAN-0074_commons_full_compliance_system_verificat_2026-03-17.md": "2026-03-18T22:55:51.288437", + "FPLAN-0051_memory_templates_modernization_spawn_han_2026-03-15.md": "2026-03-18T22:55:51.288440", + "FPLAN-0067_cli_seedgo_compliance_json_structure_wir_2026-03-17.md": "2026-03-18T22:55:51.288442", + "FPLAN-0063_seedgo_100_compliance_backup_cleanup_2026-03-17.md": "2026-03-18T22:55:51.288445", + "FPLAN-0056_three_json_pattern_redesign_2026-03-16.md": "2026-03-18T22:55:51.288447", + "FPLAN-0021_wire_dplans_into_flow_cli_router_and_cle_2026-03-10.md": "2026-03-18T22:55:51.288449", + "FPLAN-0116_heal_test_trigger_2026-03-18.md": "2026-03-18T22:55:51.288451", + "FPLAN-0089_shakedown_fplan_building_a_user_dashboard_2026-03-17.md": "2026-03-18T22:55:51.288453", + "FPLAN-0098_command_routing_fix_module_rename_2026-03-17.md": "2026-03-18T22:55:51.288456", + "FPLAN-0123_final_error_test_2026-03-18.md": "2026-03-18T22:55:51.288458", + "FPLAN-0045_memory_cli_introspection_overhaul_2026-03-15.md": "2026-03-18T22:55:51.288460", + "FPLAN-0106_monitor_phase_1_visible_degradation_polling_f_2026-03-18.md": "2026-03-18T22:55:51.288462", + "FPLAN-0020_seedgo_standard_handler_activation_2026-03-09.md": "2026-03-18T22:55:51.288464", + "FPLAN-0019_seedgo_standards_query_module_2026-03-09.md": "2026-03-18T22:55:51.288467", + "FPLAN-0094_drone_git_module_phase_1_core_build_2026-03-17.md": "2026-03-18T22:55:51.288469", + "FPLAN-0061_prax_architecture_and_readme_compliance_2026-03-17.md": "2026-03-18T22:55:51.288471", + "TPLAN-0001_test_tplan_auto_discovery_2026-03-18.md": "2026-03-18T22:55:51.288473", + "FPLAN-0092_phase_3_command_activation_management_cli_2026-03-17.md": "2026-03-18T22:55:51.288475", + "DPLAN-0014_shakedown_dplan_in_drone_2026-03-17.md": "2026-03-18T22:55:51.288477", + "FPLAN-0071_trigger_json_structure_compliance_2026-03-17.md": "2026-03-18T22:55:51.288479", + "FPLAN-0070_skills_branch_seedgo_100_compliance_2026-03-17.md": "2026-03-18T22:55:51.288481", + "DPLAN-0022_auto_discovery_template_system_remove_plan_ty_2026-03-18.md": "2026-03-18T22:55:51.288484", + "FPLAN-0042_drone_test_suite_multi_round_audit_proce_2026-03-14.md": "2026-03-18T22:55:51.288486", + "FPLAN-0038_dashboard_and_status_pipeline_full_lifec_2026-03-14.md": "2026-03-18T22:55:51.288488", + "DPLAN-0013_shakedown_dplan_notification_system_design_2026-03-17.md": "2026-03-18T22:55:51.288490", + "FPLAN-0018_seedgo_one_path_audit_architecture_2026-03-09.md": "2026-03-18T22:55:51.288492", + "TPLAN-0002_test_tplan_2026-03-18.md": "2026-03-18T22:55:51.288495", + "FPLAN-0084_phase_1_command_registry_infrastructure_2026-03-17.md": "2026-03-18T22:55:51.288497", + "FPLAN-0029_aipass_as_operating_system_2026-03-13.md": "2026-03-18T22:55:51.288499", + "FPLAN-0039_dashboard_pipeline_full_lifecycle_2026-03-14.md": "2026-03-18T22:55:51.288501", + "DPLAN-0026_template_registry_system_drone_command_regist_2026-03-18.md": "2026-03-18T22:55:51.288504", + "FPLAN-0050_subagent_auto_fix_hook_hook_cleanup_2026-03-15.md": "2026-03-18T22:55:51.288506", + "FPLAN-0127_full_output_test_2026-03-18.md": "2026-03-18T22:55:51.288508", + "FPLAN-0081_json_handler_foundation_replace_with_standard_2026-03-17.md": "2026-03-18T22:55:51.288510", + "FPLAN-0104_test_auto_discovery_fplan_2026-03-18.md": "2026-03-18T22:55:51.288512", + "FPLAN-0119_error_visibility_test_2026-03-18.md": "2026-03-18T22:55:51.288514", + "FPLAN-0048_dplan_0046_execution_seedgo_reach_standa_2026-03-15.md": "2026-03-18T22:55:51.288516", + "FPLAN-0085_test_default_fplan_with_dummy_content_about_b_2026-03-17.md": "2026-03-18T22:55:51.288519", + "FPLAN-0077_daemon_json_structure_standard_complianc_2026-03-17.md": "2026-03-18T22:55:51.288521", + "FPLAN-0096_seedgo_compliance_flow_to_100_2026-03-17.md": "2026-03-18T22:55:51.288523", + "FPLAN-0068_json_structure_compliance_prax_1_to_100_2026-03-17.md": "2026-03-18T22:55:51.288525", + "FPLAN-0107_template_registry_system_registry_ops_handler_2026-03-18.md": "2026-03-18T22:55:51.288527", + "FPLAN-0057_simplify_logging_remove_hierarchical_ret_2026-03-16.md": "2026-03-18T22:55:51.288529", + "FPLAN-0076_commons_seedgo_compliance_quick_wins_enc_2026-03-17.md": "2026-03-18T22:55:51.288531", + "FPLAN-0027_aipass_as_operating_system_2026-03-13.md": "2026-03-18T22:55:51.288535", + "FPLAN-0083_activated_commands_scan_restore_human_cli_2026-03-17.md": "2026-03-18T22:55:51.288537", + "DPLAN-0021_test_template_wiring_2026-03-18.md": "2026-03-18T22:55:51.288539", + "FPLAN-0037_backup_split_config_handler_ignore_patte_2026-03-14.md": "2026-03-18T22:55:51.288542", + "DPLAN-0016_seedgo_compliance_flow_branch_to_100_2026-03-17.md": "2026-03-18T22:55:51.288544", + "FPLAN-0046_seedgo_reach_compliance_culture_system_w_2026-03-15.md": "2026-03-18T22:55:51.288546", + "DPLAN-0005_dplan_close_test_2026-03-17.md": "2026-03-18T22:55:51.288548", + "FPLAN-0115_verify_registry_bug_2026-03-18.md": "2026-03-18T22:55:51.288550", + "FPLAN-0109_monitor_phase_2_caller_attribution_via_drone__2026-03-18.md": "2026-03-18T22:55:51.288552", + "FPLAN-0101_test_fplan_default_2026-03-18.md": "2026-03-18T22:55:51.288555", + "FPLAN-0062_wire_post_rollover_chain_in_orchestrator_2026-03-17.md": "2026-03-18T22:55:51.288557", + "DPLAN-0006_activated_commands_scan_restore_human_cli_2026-03-17.md": "2026-03-18T22:55:51.288559", + "FPLAN-0005_spawn_branch_scaffold_audit_2026-03-07.md": "2026-03-18T22:55:51.288561", + "FPLAN-0030_registry_uuid_migration_add_metadataid_t_2026-03-13.md": "2026-03-18T22:55:51.288563", + "FPLAN-0121_loud_error_test_2026-03-18.md": "2026-03-18T22:55:51.288565", + "FPLAN-0117_vector_intake_test_2026-03-18.md": "2026-03-18T22:55:51.288567", + "FPLAN-0017_implement_two_level_introspection_standa_2026-03-08.md": "2026-03-18T22:55:51.288570", + "DPLAN-0012_hook_management_seedgo_owns_hooks_cleanup_cro_2026-03-17.md": "2026-03-18T22:55:51.288572", + "FPLAN-0055_memory_subsystem_activation_triggers_log_2026-03-16.md": "2026-03-18T22:55:51.288574", + "DPLAN-0009_flow_system_shakedown_full_integration_test_2026-03-17.md": "2026-03-18T22:55:51.288576", + "FPLAN-0120_cache_cleared_test_2026-03-18.md": "2026-03-18T22:55:51.288578", + "FPLAN-0088_phase_2_module_scanning_2026-03-17.md": "2026-03-18T22:55:51.288580", + "FPLAN-0103_auto_discovery_template_refactor_2026-03-18.md": "2026-03-18T22:55:51.288582", + "FPLAN-0075_auto_fix_execution_restoring_determinist_2026-03-17.md": "2026-03-18T22:55:51.288584", + "FPLAN-0043_whitelist_file_size_cap_for_backup_scann_2026-03-14.md": "2026-03-18T22:55:51.288587", + "DPLAN-0023_test_auto_discovery_dplan_2026-03-18.md": "2026-03-18T22:55:51.288589", + "FPLAN-0044_aipass_front_door_user_discovery_entry_p_2026-03-15.md": "2026-03-18T22:55:51.288591", + "DPLAN-0024_design_investigation_2026-03-18.md": "2026-03-18T22:55:51.288593", + "FPLAN-0114_auto_heal_test_2026-03-18.md": "2026-03-18T22:55:51.288595", + "FPLAN-0108_registry_integration_test_fplan_2026-03-18.md": "2026-03-18T22:55:51.288597", + "FPLAN-0035_backup_operational_snapshot_versioned_2026-03-14.md": "2026-03-18T22:55:51.288600", + "FPLAN-0091_shakedown_fplan_in_drone_2026-03-17.md": "2026-03-18T22:55:51.288602", + "FPLAN-0009_citizen_classes_template_system_passport_2026-03-07.md": "2026-03-18T22:55:51.288604", + "FPLAN-0053_hook_migration_project_level_settings_2026-03-16.md": "2026-03-18T22:55:51.288606", + "FPLAN-0125_flag_trace_test_2026-03-18.md": "2026-03-18T22:55:51.288608", + "FPLAN-0072_unified_plan_pipeline_fplandplan_parity__2026-03-17.md": "2026-03-18T22:55:51.288610", + "FPLAN-0052_templates_module_modernization_spawn_han_2026-03-15.md": "2026-03-18T22:55:51.288612", + "FPLAN-0131_final_e2e_test_2026-03-18.md": "2026-03-18T23:28:27.324501" } \ No newline at end of file diff --git a/src/aipass/memory/config/memory_bank.config.json b/src/aipass/memory/config/memory_bank.config.json index daeed13e..03d40c30 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/backup/processed_plans", "supported_extensions": [".md"], "collection_name": "flow_plans" }