feat(flow): feat(flow): foreground archival, --dry-run, template auto-heal, vector verify pipeline (#97)
* feat(prax): monitor restoration phases 1-3, dead module cleanup, help cleanup Co-Authored-By: @prax <prax@aipass> * feat(prax): introspection 100%, dead module cleanup, stale logs removed Co-Authored-By: @prax <prax@aipass> * feat(memory): config fix + verify command for plan vectorization Co-Authored-By: @memory <memory@aipass> * feat(memory): fix plan vectorization: batch subprocess, decouple from startup, add process-plans command Co-Authored-By: @memory <memory@aipass> * feat(memory): update plans_processed manifest after batch vectorization of 113 files Co-Authored-By: @memory <memory@aipass> * feat(flow): feat(flow): foreground archival, --dry-run, template auto-heal, vector verify pipeline Co-Authored-By: @flow <flow@aipass> --------- Co-authored-by: @prax <prax@aipass> Co-authored-by: @memory <memory@aipass> Co-authored-by: @flow <flow@aipass>
This commit is contained in:
co-authored by
@prax
@memory
@flow
parent
d714535ad3
commit
55080d5452
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# {plan_number}: {subject}
|
||||
|
||||
Tag: {tag}
|
||||
|
||||
> Testing template — auto-discovered from filesystem
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
---
|
||||
*Created: {today}*
|
||||
*Updated: {today}*
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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}'}
|
||||
|
||||
|
||||
@@ -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
|
||||
# =============================================================================
|
||||
|
||||
@@ -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 <plan_label> -> 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 <plan_label>")
|
||||
console.print()
|
||||
console.print("[bold]COMMANDS:[/bold]")
|
||||
console.print(" [cyan]verify <plan_label>[/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)
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user