diff --git a/src/aipass/prax/CLOSED_PLANS.local.json b/src/aipass/prax/CLOSED_PLANS.local.json index 089f71cd..3b8d6ce8 100644 --- a/src/aipass/prax/CLOSED_PLANS.local.json +++ b/src/aipass/prax/CLOSED_PLANS.local.json @@ -13,6 +13,13 @@ "subject": "Seedgo log_structure standard update for hierarchical placement", "date_closed": "2026-03-09", "location": "prax" + }, + { + "plan_id": "FPLAN-0039", + "type": "FPLAN", + "subject": "Dashboard Pipeline — Full Lifecycle", + "date_closed": "2026-03-18", + "location": "prax" } ] } diff --git a/src/aipass/prax/README.md b/src/aipass/prax/README.md index d0f56a03..119a4db6 100644 --- a/src/aipass/prax/README.md +++ b/src/aipass/prax/README.md @@ -85,16 +85,16 @@ prax/ │ ├── prax.py # Entry point (CLI) │ ├── modules/ │ │ ├── logger.py # SystemLogger (public API) -│ │ ├── monitor_module.py # Mission Control -│ │ ├── agent_status_module.py # Agent status overview +│ │ ├── monitor.py # Mission Control +│ │ ├── agent_status.py # Agent status overview │ │ ├── dashboard.py # System dashboard -│ │ ├── discover_module.py # Module discovery -│ │ ├── init_module.py # Logging system initialization -│ │ ├── log_audit_module.py # Log file audit -│ │ ├── run_module.py # Continuous logging mode -│ │ ├── shutdown_module.py # Logging system shutdown -│ │ ├── status_module.py # System status / STATUS sync -│ │ └── terminal_module.py # Terminal output toggle +│ │ ├── discover.py # Module discovery +│ │ ├── init_prax.py # Logging system initialization +│ │ ├── log_audit.py # Log file audit +│ │ ├── run.py # Continuous logging mode +│ │ ├── shutdown.py # Logging system shutdown +│ │ ├── status.py # System status / STATUS sync +│ │ └── terminal.py # Terminal output toggle │ └── handlers/ │ ├── central/ # Central file reader │ ├── config/ # Configuration loading diff --git a/src/aipass/prax/apps/handlers/config/load.py b/src/aipass/prax/apps/handlers/config/load.py index b59878d7..80a5477f 100755 --- a/src/aipass/prax/apps/handlers/config/load.py +++ b/src/aipass/prax/apps/handlers/config/load.py @@ -74,18 +74,35 @@ def get_system_logs_dir() -> Path: def get_module_logs_dir(module_name: str) -> Path: """Get the branch-root logs directory for a module. - Returns ECOSYSTEM_ROOT / module_name / "logs", creating it if needed. + Checks ECOSYSTEM_ROOT (src/aipass/) first, then SRC_ROOT (src/) for + branches that live outside the aipass namespace (e.g., commons). This is the primary local log directory resolver for the two-tier model (system_logs/ for central aggregation + branch-root logs/ for local debugging). Args: - module_name: Module name (e.g., "flow", "prax", "trigger") + module_name: Module name (e.g., "flow", "prax", "commons") Returns: Path to the module's branch-root logs directory """ - logs_dir = ECOSYSTEM_ROOT / module_name / "logs" + # Standard: src/aipass/{module}/logs + branch_dir = ECOSYSTEM_ROOT / module_name + if branch_dir.exists(): + logs_dir = branch_dir / "logs" + logs_dir.mkdir(parents=True, exist_ok=True) + return logs_dir + + # Fallback: src/{module}/logs for branches outside aipass namespace + src_root = ECOSYSTEM_ROOT.parent + alt_branch_dir = src_root / module_name + if alt_branch_dir.exists(): + logs_dir = alt_branch_dir / "logs" + logs_dir.mkdir(parents=True, exist_ok=True) + return logs_dir + + # Default: create under ECOSYSTEM_ROOT (original behavior for new branches) + logs_dir = branch_dir / "logs" logs_dir.mkdir(parents=True, exist_ok=True) return logs_dir diff --git a/src/aipass/prax/apps/handlers/logging/introspection.py b/src/aipass/prax/apps/handlers/logging/introspection.py index 63b69932..1b2ab1ed 100755 --- a/src/aipass/prax/apps/handlers/logging/introspection.py +++ b/src/aipass/prax/apps/handlers/logging/introspection.py @@ -78,17 +78,20 @@ def get_calling_module_path() -> Optional[str]: del frame _AIPASS_PKG_ROOT = Path(__file__).resolve().parents[4] # logging/ → handlers/ → apps/ → prax/ → aipass/ +_SRC_ROOT = _AIPASS_PKG_ROOT.parent # aipass/ → src/ (contains branches outside aipass namespace) def detect_branch_from_path(module_path: str) -> Optional[str]: """Detect branch name from module file path The package structure is: .../src/aipass/{module}/apps/... Resolves relative to the aipass package root found via __file__. + Also handles branches outside src/aipass/ (e.g., src/commons/). Examples: .../src/aipass/flow/apps/module.py → "flow" .../src/aipass/prax/apps/module.py → "prax" .../src/aipass/drone/apps/branch.py → "drone" + .../src/commons/apps/module.py → "commons" Returns: Module/branch name (e.g., "flow") or None @@ -96,8 +99,10 @@ def detect_branch_from_path(module_path: str) -> Optional[str]: if not module_path: return None + path = Path(module_path).resolve() + + # Primary: src/aipass/{branch}/... try: - path = Path(module_path).resolve() relative = path.relative_to(_AIPASS_PKG_ROOT) # relative is like: flow/apps/module.py → parts[0] = "flow" if len(relative.parts) >= 2: @@ -107,4 +112,14 @@ def detect_branch_from_path(module_path: str) -> Optional[str]: except ValueError: pass + # Fallback: src/{branch}/... for branches outside src/aipass/ (e.g., commons) + try: + relative = path.relative_to(_SRC_ROOT) + if len(relative.parts) >= 2 and relative.parts[0] != "aipass": + branch = relative.parts[0] + json_handler.log_operation("introspection_resolved", {"module_path": module_path, "branch": branch, "outside_aipass": True}) + return branch + except ValueError: + pass + return None diff --git a/src/aipass/prax/apps/handlers/logging/setup.py b/src/aipass/prax/apps/handlers/logging/setup.py index 763bf833..d0839b16 100755 --- a/src/aipass/prax/apps/handlers/logging/setup.py +++ b/src/aipass/prax/apps/handlers/logging/setup.py @@ -104,7 +104,7 @@ def setup_individual_logger(module_name: str) -> logging.Logger: if branch_path: branch_name = Path(branch_path).name else: - branch_name = "prax" # Fallback to prax if no branch detected + branch_name = "unknown_branch" # Visible fallback — never silently route to prax # Create formatter (shared by all handlers) formatter = logging.Formatter( diff --git a/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py b/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py index 5b45649c..04eba676 100644 --- a/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py +++ b/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py @@ -20,7 +20,7 @@ Flow: 5. Include branch attribution in all events Thread Safety: -- Designed to run in monitor_module.py's file watcher thread +- Designed to run in monitor.py's file watcher thread - Uses MonitoringQueue's thread-safe enqueue - Watchdog observers run in their own threads diff --git a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py index b1fa3c6f..d0e263d8 100644 --- a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py +++ b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py @@ -14,7 +14,7 @@ Watchdog FileSystemEventHandler that processes filesystem events and: 2. Fires trigger events for cross-module integration 3. Parses Claude Code JSONL sessions for agent activity tracking -Extracted from monitor_module.py to maintain 3-layer architecture: +Extracted from monitor.py to maintain 3-layer architecture: module (orchestration) -> handler (implementation) """ diff --git a/src/aipass/prax/apps/handlers/monitoring/log_watcher.py b/src/aipass/prax/apps/handlers/monitoring/log_watcher.py index d9dde4da..bf84cd55 100644 --- a/src/aipass/prax/apps/handlers/monitoring/log_watcher.py +++ b/src/aipass/prax/apps/handlers/monitoring/log_watcher.py @@ -493,15 +493,16 @@ class LogFileWatcher(FileSystemEventHandler): logger.info(f"Error replaying {log_file}: {e}") -def start_log_watcher(event_queue: MonitoringQueue) -> Any: +def start_log_watcher(event_queue: MonitoringQueue, use_polling: bool = False) -> Any: """ Start watching log files and pushing events to queue. Args: event_queue: MonitoringQueue instance for event delivery + use_polling: If True, use PollingObserver instead of native inotify Returns: - Observer instance (caller must keep alive and call .stop() on shutdown) + Observer instance """ global _log_observer @@ -513,18 +514,28 @@ def start_log_watcher(event_queue: MonitoringQueue) -> Any: # Create watcher instance watcher = LogFileWatcher(event_queue) - # Soft start: seek to end of all logs, only show NEW activity + # Replay recent lines for startup context (before seeking to EOF) + watcher.replay_recent(num_lines=2) + + # Soft start: seek to end of all logs, only show NEW activity after this watcher.initialize_positions() - # Create and start observer - observer = WatchdogObserver() + # Create observer — polling fallback when inotify unavailable + if use_polling: + from watchdog.observers.polling import PollingObserver + observer = PollingObserver(timeout=1) + logger.info("Log watcher using polling observer (1s interval)") + else: + observer = WatchdogObserver() + observer.schedule(watcher, str(get_system_logs_dir()), recursive=False) observer.start() _log_observer = observer - json_handler.log_operation("log_watcher_started", {"log_dir": str(get_system_logs_dir())}) - logger.info(f"Log watcher started, monitoring: {get_system_logs_dir()}") + mode = "polling" if use_polling else "inotify" + json_handler.log_operation("log_watcher_started", {"log_dir": str(get_system_logs_dir()), "mode": mode}) + logger.info(f"Log watcher started ({mode}), monitoring: {get_system_logs_dir()}") return observer diff --git a/src/aipass/prax/apps/modules/agent_status_module.py b/src/aipass/prax/apps/modules/agent_status.py similarity index 97% rename from src/aipass/prax/apps/modules/agent_status_module.py rename to src/aipass/prax/apps/modules/agent_status.py index cb3ad344..274d3de7 100644 --- a/src/aipass/prax/apps/modules/agent_status_module.py +++ b/src/aipass/prax/apps/modules/agent_status.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: agent_status_module.py +# Name: agent_status.py # Description: PRAX Agent Status Push Command # Version: 0.1.0 # Created: 2026-02-25 @@ -73,10 +73,6 @@ def handle_command(command: str, args: List[str]) -> bool: if command != 'agent-status-push': return False - if not args: - print_introspection() - return True - from aipass.prax.apps.handlers.dashboard.agent_status_writer import ( build_agent_status_section, push_agent_status_dashboard, diff --git a/src/aipass/prax/apps/modules/dashboard.py b/src/aipass/prax/apps/modules/dashboard.py index 8e22e6e5..021750c3 100644 --- a/src/aipass/prax/apps/modules/dashboard.py +++ b/src/aipass/prax/apps/modules/dashboard.py @@ -12,7 +12,7 @@ Dashboard Section Utilities Provides utilities for services to update their sections in branch DASHBOARD.local.json files. Each service manages only its own section. -Run directly or via: python3 apps/modules/dashboard.py +Run via: drone @prax dashboard """ __all__ = [ @@ -162,7 +162,7 @@ def print_introspection(): for section in DASHBOARD_TEMPLATE["sections"]: console.print(f" - {section}") console.print() - console.print("[dim]Run 'python3 dashboard.py --help' for usage[/dim]") + console.print("[dim]Run 'drone @prax dashboard --help' for usage[/dim]") console.print() @@ -465,6 +465,10 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + subcmd = args[0] json_handler.log_operation("dashboard_command", {"subcommand": subcmd}) diff --git a/src/aipass/prax/apps/modules/discover_module.py b/src/aipass/prax/apps/modules/discover.py similarity index 53% rename from src/aipass/prax/apps/modules/discover_module.py rename to src/aipass/prax/apps/modules/discover.py index e2adc828..5c90e7b3 100755 --- a/src/aipass/prax/apps/modules/discover_module.py +++ b/src/aipass/prax/apps/modules/discover.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: discover_module.py +# Name: discover.py # Description: PRAX Discover Command # Version: 1.0.0 # Created: 2025-11-15 @@ -15,28 +15,33 @@ Implements the 'discover' command using handle_command interface. import sys from typing import List -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error from aipass.prax.apps.handlers.discovery.scanner import discover_python_modules from aipass.prax.apps.handlers.json import json_handler def print_help(): - """Display module help and connected handlers""" + """Drone-compliant help output - command syntax and examples""" console.print() - console.print("[bold cyan]PRAX Discover Module[/bold cyan]") - console.print() - console.print("[yellow]Connected Handlers:[/yellow]") + console.print("[bold cyan]PRAX Discover - Scan Python Modules[/bold cyan]") console.print() - console.print(" [cyan]prax/modules/[/cyan]") - console.print(" [dim]- logger.py[/dim] (system_logger)") + console.print("[yellow]Purpose:[/yellow]") + console.print(" Scan and register Python modules in the AIPass ecosystem") console.print() - console.print(" [cyan]prax/handlers/discovery/[/cyan]") - console.print(" [dim]- scanner.py[/dim] (discover_python_modules)") + console.print("[yellow]Usage:[/yellow]") + console.print(" drone @prax discover scan") console.print() - console.print("[dim]Run 'python3 discover_module.py --help' for usage[/dim]") + console.print("[yellow]Subcommands:[/yellow]") + console.print(" scan Discover and register Python modules") + console.print() + + console.print("[yellow]Examples:[/yellow]") + console.print() + console.print(" [dim]# Discover Python modules[/dim]") + console.print(" $ drone @prax discover scan") console.print() @@ -58,24 +63,39 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True - json_handler.log_operation("discover_command_executed", {"command": command}) - console.print("🔍 Discovering Python modules...") - modules = discover_python_modules() - console.print(f"✅ Discovered {len(modules)} modules") + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + + if args[0] == 'scan': + json_handler.log_operation("discover_command_executed", {"command": command}) + console.print("🔍 Discovering Python modules...") + modules = discover_python_modules() + console.print(f"✅ Discovered {len(modules)} modules") + return True + + error(f"Unknown discover subcommand: {args[0]}") + print_help() return True def print_introspection(): - """Display module introspection info.""" + """Display module introspection - shows connected handlers""" console.print() - console.print("discover_module Module") - console.print("Implements the 'discover' command to scan and register Python modules") + console.print("[bold cyan]PRAX Discover Module[/bold cyan]") console.print() - console.print("Connected Handlers:") - console.print(" modules/") - console.print(" - logger.py (system_logger — auto-routing logger for module log files)") - console.print(" handlers/discovery/") - console.print(" - scanner.py (discover_python_modules — scans filesystem for Python modules)") + console.print("[yellow]Connected Handlers:[/yellow]") + console.print() + + console.print(" [cyan]prax/modules/[/cyan]") + console.print(" [dim]- logger.py[/dim] (system_logger)") + console.print() + + console.print(" [cyan]prax/handlers/discovery/[/cyan]") + console.print(" [dim]- scanner.py[/dim] (discover_python_modules)") + console.print() + + console.print("[dim]Run 'drone @prax discover --help' for usage[/dim]") console.print() diff --git a/src/aipass/prax/apps/modules/init_module.py b/src/aipass/prax/apps/modules/init_prax.py similarity index 69% rename from src/aipass/prax/apps/modules/init_module.py rename to src/aipass/prax/apps/modules/init_prax.py index be364817..3535fcb4 100755 --- a/src/aipass/prax/apps/modules/init_module.py +++ b/src/aipass/prax/apps/modules/init_prax.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: init_module.py +# Name: init_prax.py # Description: PRAX Init Command # Version: 1.0.0 # Created: 2025-11-15 @@ -32,7 +32,7 @@ def print_introspection(): console.print(" [dim]- logger.py[/dim] (initialize_logging_system, system_logger)") console.print() - console.print("[dim]Run 'python3 init_module.py --help' for usage[/dim]") + console.print("[dim]Run 'drone @prax init --help' for usage[/dim]") console.print() @@ -46,13 +46,18 @@ def print_help(): console.print(" Initialize the PRAX logging system for monitoring AIPass operations") console.print() - console.print("[yellow]Usage Examples:[/yellow]") + console.print("[yellow]Usage:[/yellow]") + console.print(" drone @prax init start") + console.print() + + console.print("[yellow]Subcommands:[/yellow]") + console.print(" start Initialize the PRAX logging system") + console.print() + + console.print("[yellow]Examples:[/yellow]") console.print() console.print(" [dim]# Initialize logging system[/dim]") - console.print(" $ prax init") - console.print() - console.print(" [dim]# Standalone execution[/dim]") - console.print(" $ python3 init_module.py") + console.print(" $ drone @prax init start") console.print() @@ -74,17 +79,26 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True - try: - json_handler.log_operation("init_command_executed", {"command": command}) - console.print("🚀 Initializing PRAX logging system...") - initialize_logging_system() - console.print("✅ PRAX logging system initialized") + if args[0] in ('--help', '-h', 'help'): + print_help() return True - except Exception as e: - logger.error(f"Error in init command: {e}") - error(str(e)) - return True + if args[0] == 'start': + try: + json_handler.log_operation("init_command_executed", {"command": command}) + console.print("🚀 Initializing PRAX logging system...") + initialize_logging_system() + console.print("✅ PRAX logging system initialized") + return True + + except Exception as e: + logger.error(f"Error in init command: {e}") + error(str(e)) + return True + + error(f"Unknown init subcommand: {args[0]}") + print_help() + return True if __name__ == "__main__": diff --git a/src/aipass/prax/apps/modules/log_audit_module.py b/src/aipass/prax/apps/modules/log_audit.py similarity index 84% rename from src/aipass/prax/apps/modules/log_audit_module.py rename to src/aipass/prax/apps/modules/log_audit.py index dca7af35..ff9b7d01 100644 --- a/src/aipass/prax/apps/modules/log_audit_module.py +++ b/src/aipass/prax/apps/modules/log_audit.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: log_audit_module.py +# Name: log_audit.py # Description: PRAX Log Audit Command # Version: 0.1.0 # Created: 2026-02-26 @@ -37,7 +37,7 @@ def print_introspection(): console.print(" [dim]- log_watchdog.py (scan_log_files, enforce_log_limits, log_health_summary)[/dim]") console.print() - console.print("[dim]Run 'drone @prax log-audit' for status, 'drone @prax log-audit enforce' to truncate[/dim]") + console.print("[dim]Run 'drone @prax log-audit --help' for usage[/dim]") console.print() @@ -51,10 +51,15 @@ def print_help(): console.print(" Scan system_logs/ for oversized files and enforce rotation limits") console.print() + console.print("[yellow]Subcommands:[/yellow]") + console.print() + console.print(" [cyan]audit[/cyan] Show log health summary + any oversized files") + console.print(" [cyan]enforce[/cyan] Truncate all oversized files to 1000 lines") + console.print() console.print("[yellow]Usage:[/yellow]") console.print() console.print(" [dim]# Show log health summary + any oversized files[/dim]") - console.print(" $ drone @prax log-audit") + console.print(" $ drone @prax log-audit audit") console.print() console.print(" [dim]# Truncate all oversized files to 1000 lines[/dim]") console.print(" $ drone @prax log-audit enforce") @@ -108,15 +113,25 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + from aipass.prax.apps.handlers.logging.log_watchdog import ( scan_log_files, enforce_log_limits, log_health_summary, ) - json_handler.log_operation("log_audit_executed", {"mode": args[0] if args else "audit"}) + subcmd = args[0] + json_handler.log_operation("log_audit_executed", {"mode": subcmd}) - if args and args[0] == 'enforce': + if subcmd == 'audit': + files = scan_log_files() + summary = log_health_summary() + _display_audit(files, summary) + return True + elif subcmd == 'enforce': console.print("\n[bold cyan]Enforcing log limits...[/bold cyan]") actions = enforce_log_limits() @@ -134,12 +149,10 @@ def handle_command(command: str, args: List[str]) -> bool: console.print() logger.info("[log-audit] Enforced limits on %d files", len(actions)) return True - - # Default: audit mode - files = scan_log_files() - summary = log_health_summary() - _display_audit(files, summary) - return True + else: + error(f"Unknown log-audit subcommand: {subcmd}") + print_help() + return True if __name__ == "__main__": diff --git a/src/aipass/prax/apps/modules/monitor_module.py b/src/aipass/prax/apps/modules/monitor.py similarity index 73% rename from src/aipass/prax/apps/modules/monitor_module.py rename to src/aipass/prax/apps/modules/monitor.py index ffff7aa5..6b4a3d28 100755 --- a/src/aipass/prax/apps/modules/monitor_module.py +++ b/src/aipass/prax/apps/modules/monitor.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: monitor_module.py +# Name: monitor.py # Description: Unified Monitoring Module # Version: 0.3.0 # Created: 2025-11-23 @@ -22,9 +22,9 @@ Purpose: Enables multi-agent workflow visibility and coordination. Usage: - prax monitor # Monitor all branches (quiet mode) - prax monitor all # Explicit all-branches monitoring - prax monitor seed,cli # Monitor specific branches + drone @prax monitor # Show introspection + drone @prax monitor run # Monitor all branches + drone @prax monitor run seed,cli # Monitor specific branches Interactive Commands: help # Show available commands @@ -50,6 +50,7 @@ import sys import argparse import threading import time +from datetime import datetime from pathlib import Path from typing import List, Optional @@ -68,6 +69,7 @@ from aipass.prax.apps.handlers.monitoring import ( MonitoringQueue, # event_queue.py ModuleTracker, # module_tracker.py ) +from aipass.prax.apps.handlers.monitoring.event_queue import MonitoringEvent # NOTE: FileSystemEventHandler implementation lives in: # aipass.prax.apps.handlers.monitoring.filesystem_handler.MonitoringFileHandler # It handles trigger events for file_created/file_deleted/file_modified/file_moved @@ -202,10 +204,29 @@ def handle_command(command: str, args: List[str]) -> bool: if command != 'monitor': return False + # Introspection gate — bare command shows module info if not args: print_introspection() return True + # Help intercept + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + + # Subcommand routing + subcmd = args[0] + if subcmd == 'run': + return _run_monitor(args[1:]) + + # Unknown subcommand + error(f"Unknown monitor subcommand: {subcmd}") + print_help() + return True + + +def _run_monitor(args: List[str]) -> bool: + """Launch Mission Control live monitoring.""" global _monitoring_active, _event_queue, _module_tracker global _display_thread, _file_watcher_thread, _log_watcher_thread @@ -304,6 +325,47 @@ def _display_worker(): print_event(event.event_type, event.branch, event.message, event.level, pid=branch_pid) +def _get_watch_directories(repo_root: Path) -> list[tuple[Path, bool]]: + """Get targeted directories to watch instead of entire repo root. + + Returns (path, recursive) tuples. Watches apps/ recursively for code, + branch roots non-recursively for STATUS/README, and .trinity/ for identity. + + Some branches (backup, memory) have 10,000+ dirs in data stores. + Watching only apps/ keeps inotify count under ~800. + """ + dirs: list[tuple[Path, bool]] = [] + + # Load branch paths from registry + registry_path = repo_root / "AIPASS_REGISTRY.json" + if registry_path.exists(): + try: + data = _json.loads(registry_path.read_text(encoding="utf-8")) + for branch in data.get("branches", []): + branch_path = repo_root / branch.get("path", "") + if not branch_path.exists(): + continue + # apps/ recursive — source code changes + apps_dir = branch_path / "apps" + if apps_dir.exists(): + dirs.append((apps_dir, True)) + # Branch root non-recursive — STATUS.local.md, README.md + dirs.append((branch_path, False)) + # .trinity/ non-recursive — identity files + trinity_dir = branch_path / ".trinity" + if trinity_dir.exists(): + dirs.append((trinity_dir, False)) + except (ValueError, OSError) as e: + logger.warning(f"[monitor] Failed to read registry: {e}") + + # Watch Claude Code project sessions for agent activity tracking + claude_projects = Path.home() / ".claude" / "projects" + if claude_projects.exists(): + dirs.append((claude_projects, True)) + + return dirs + + def _file_watcher_worker(): """File watcher thread - watches filesystem changes and pushes to queue""" global _monitoring_active, _event_queue @@ -311,24 +373,64 @@ def _file_watcher_worker(): from watchdog.observers import Observer from aipass.prax.apps.handlers.monitoring.filesystem_handler import MonitoringFileHandler - # Files whose modification indicates a command is running (python3 direct calls) - # Maps filename -> command description. Used to emit command separators from file events. COMMAND_INDICATOR_FILES = { 'standards_audit_log.json': 'seed audit', 'standards_checklist_log.json': 'seed checklist', } - # Create observer and start watching - # Watch from repo root (covers all modules) - from aipass.prax.apps.handlers.config.load import _find_repo_root - observer = Observer() handler = MonitoringFileHandler( event_queue=_event_queue, command_indicator_files=COMMAND_INDICATOR_FILES, ) - watch_dir = _find_repo_root() - observer.schedule(handler, str(watch_dir), recursive=True) - observer.start() + + from aipass.prax.apps.handlers.config.load import _find_repo_root + repo_root = _find_repo_root() + watch_dirs = _get_watch_directories(repo_root) + + if not watch_dirs: + logger.error("[monitor] No watch directories found — file watcher disabled") + if _event_queue: + _event_queue.enqueue(MonitoringEvent( + priority=2, event_type='log', branch='PRAX', + action='warning', level='warning', timestamp=datetime.now(), + message="File watcher: no watch directories found — file events disabled" + )) + return + + observer = Observer() + for watch_dir, recursive in watch_dirs: + observer.schedule(handler, str(watch_dir), recursive=recursive) + + logger.info(f"[monitor] File watcher: {len(watch_dirs)} watches scheduled") + + try: + observer.start() + except OSError as e: + logger.warning(f"[monitor] inotify unavailable: {e} — switching to polling") + if _event_queue: + _event_queue.enqueue(MonitoringEvent( + priority=2, event_type='log', branch='PRAX', + action='warning', level='warning', timestamp=datetime.now(), + message="File watcher: inotify unavailable — using polling fallback (slower)" + )) + + # Fallback to PollingObserver + try: + from watchdog.observers.polling import PollingObserver + observer = PollingObserver(timeout=2) + for watch_dir, recursive in watch_dirs: + observer.schedule(handler, str(watch_dir), recursive=recursive) + observer.start() + logger.info("[monitor] File watcher: polling fallback active") + except Exception as e2: + logger.error(f"[monitor] Polling fallback also failed: {e2}") + if _event_queue: + _event_queue.enqueue(MonitoringEvent( + priority=1, event_type='log', branch='PRAX', + action='error', level='error', timestamp=datetime.now(), + message="File watcher: completely unavailable — no file events" + )) + return try: while _monitoring_active: @@ -344,12 +446,31 @@ def _log_watcher_worker(): from aipass.prax.apps.handlers.monitoring.log_watcher import start_log_watcher, stop_log_watcher - # Start the proper log watcher (has command detection, branch detection, message parsing) - # Guard against None - should never happen since we initialize before starting threads if _event_queue is None: logger.error("[monitor] Event queue not initialized for log watcher") return - _observer = start_log_watcher(_event_queue) + + try: + start_log_watcher(_event_queue) + except OSError as e: + logger.warning(f"[monitor] Log watcher inotify failed: {e} — switching to polling") + if _event_queue: + _event_queue.enqueue(MonitoringEvent( + priority=2, event_type='log', branch='PRAX', + action='warning', level='warning', timestamp=datetime.now(), + message="Log watcher: inotify unavailable — using polling fallback (slower)" + )) + try: + start_log_watcher(_event_queue, use_polling=True) + except Exception as e2: + logger.error(f"[monitor] Log watcher polling fallback failed: {e2}") + if _event_queue: + _event_queue.enqueue(MonitoringEvent( + priority=1, event_type='log', branch='PRAX', + action='error', level='error', timestamp=datetime.now(), + message="Log watcher: completely unavailable — no log events" + )) + return try: while _monitoring_active: @@ -383,7 +504,7 @@ def _interactive_loop(): if not user_input: continue - cmd, cmd_args = parse_command(user_input) + cmd, _cmd_args = parse_command(user_input) if not cmd: continue @@ -460,7 +581,7 @@ def print_introspection(): console.print(" [green]STATUS: Active - watches *.log files for new entries[/green]") console.print() - console.print("[dim]Run 'python3 monitor_module.py --help' for usage[/dim]") + console.print("[dim]Run 'drone @prax monitor --help' for usage[/dim]") console.print() @@ -476,15 +597,21 @@ def print_help(): console.print("[yellow]Commands:[/yellow]") console.print() - console.print(" [cyan]monitor[/cyan]") - console.print(" Start monitoring all branches (quiet mode)") + console.print(" [cyan]drone @prax monitor[/cyan]") + console.print(" Show module introspection") console.print() - console.print(" [cyan]monitor all[/cyan]") + console.print(" [cyan]drone @prax monitor run[/cyan]") + console.print(" Start monitoring all branches") + console.print() + console.print(" [cyan]drone @prax monitor run all[/cyan]") console.print(" Explicit all-branches monitoring") console.print() - console.print(" [cyan]monitor [branches][/cyan]") + console.print(" [cyan]drone @prax monitor run [branches][/cyan]") console.print(" Monitor specific branches (comma-separated)") - console.print(" Example: monitor seed,cli,flow") + console.print(" Example: drone @prax monitor run seed,cli,flow") + console.print() + console.print(" [cyan]drone @prax monitor --help[/cyan]") + console.print(" Show this help") console.print() console.print("[yellow]Interactive Mode Commands:[/yellow]") @@ -498,13 +625,10 @@ def print_help(): console.print("[yellow]Examples:[/yellow]") console.print() console.print(" [dim]# Monitor all branches[/dim]") - console.print(" $ prax monitor") + console.print(" $ drone @prax monitor run") console.print() console.print(" [dim]# Monitor specific branches[/dim]") - console.print(" $ prax monitor seed,cli,flow") - console.print() - console.print(" [dim]# Standalone execution[/dim]") - console.print(" $ python3 monitor_module.py") + console.print(" $ drone @prax monitor run seed,cli,flow") console.print() diff --git a/src/aipass/prax/apps/modules/run_module.py b/src/aipass/prax/apps/modules/run.py similarity index 51% rename from src/aipass/prax/apps/modules/run_module.py rename to src/aipass/prax/apps/modules/run.py index 4bfc043d..1685c1f2 100755 --- a/src/aipass/prax/apps/modules/run_module.py +++ b/src/aipass/prax/apps/modules/run.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: run_module.py +# Name: run.py # Description: PRAX Run Command # Version: 1.0.0 # Created: 2025-11-15 @@ -16,23 +16,32 @@ import sys from typing import List from aipass.prax.apps.modules.logger import start_continuous_logging -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error from aipass.prax.apps.handlers.json import json_handler def print_help(): - """Display module help and connected handlers""" + """Drone-compliant help output - command syntax and examples""" console.print() - console.print("[bold cyan]PRAX Run Module[/bold cyan]") - console.print() - console.print("[yellow]Connected Handlers:[/yellow]") + console.print("[bold cyan]PRAX Run - Continuous Logging Mode[/bold cyan]") console.print() - console.print(" [cyan]prax/modules/[/cyan]") - console.print(" [dim]- logger.py[/dim] (start_continuous_logging, system_logger)") + console.print("[yellow]Purpose:[/yellow]") + console.print(" Start PRAX continuous logging mode for monitoring AIPass operations") console.print() - console.print("[dim]Run 'python3 run_module.py --help' for usage[/dim]") + console.print("[yellow]Usage:[/yellow]") + console.print(" drone @prax run start") + console.print() + + console.print("[yellow]Subcommands:[/yellow]") + console.print(" start Start continuous logging mode") + console.print() + + console.print("[yellow]Examples:[/yellow]") + console.print() + console.print(" [dim]# Start continuous logging[/dim]") + console.print(" $ drone @prax run start") console.print() @@ -54,22 +63,34 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True - json_handler.log_operation("run_command_executed", {"command": command}) - console.print("🚀 Starting PRAX continuous logging mode...") - start_continuous_logging() + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + + if args[0] == 'start': + json_handler.log_operation("run_command_executed", {"command": command}) + console.print("🚀 Starting PRAX continuous logging mode...") + start_continuous_logging() + return True + + error(f"Unknown run subcommand: {args[0]}") + print_help() return True def print_introspection(): - """Display module introspection info.""" + """Display module introspection - shows connected handlers""" console.print() - console.print("run_module Module") - console.print("Implements the 'run' command to start PRAX continuous logging mode") + console.print("[bold cyan]PRAX Run Module[/bold cyan]") console.print() - console.print("Connected Handlers:") - console.print(" modules/") - console.print(" - logger.py (start_continuous_logging — starts background logging loop)") - console.print(" - logger.py (system_logger — auto-routing logger for module log files)") + console.print("[yellow]Connected Handlers:[/yellow]") + console.print() + + console.print(" [cyan]prax/modules/[/cyan]") + console.print(" [dim]- logger.py[/dim] (start_continuous_logging, system_logger)") + console.print() + + console.print("[dim]Run 'drone @prax run --help' for usage[/dim]") console.print() diff --git a/src/aipass/prax/apps/modules/shutdown_module.py b/src/aipass/prax/apps/modules/shutdown.py similarity index 51% rename from src/aipass/prax/apps/modules/shutdown_module.py rename to src/aipass/prax/apps/modules/shutdown.py index 7333b632..8363a3b1 100755 --- a/src/aipass/prax/apps/modules/shutdown_module.py +++ b/src/aipass/prax/apps/modules/shutdown.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: shutdown_module.py +# Name: shutdown.py # Description: PRAX Shutdown Command # Version: 1.0.0 # Created: 2025-11-15 @@ -16,23 +16,32 @@ import sys from typing import List from aipass.prax.apps.modules.logger import shutdown_logging_system -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error from aipass.prax.apps.handlers.json import json_handler def print_help(): - """Display module help and connected handlers""" + """Drone-compliant help output - command syntax and examples""" console.print() - console.print("[bold cyan]PRAX Shutdown Module[/bold cyan]") - console.print() - console.print("[yellow]Connected Handlers:[/yellow]") + console.print("[bold cyan]PRAX Shutdown - Stop Logging System[/bold cyan]") console.print() - console.print(" [cyan]prax/modules/[/cyan]") - console.print(" [dim]- logger.py[/dim] (shutdown_logging_system, system_logger)") + console.print("[yellow]Purpose:[/yellow]") + console.print(" Cleanly stop the PRAX logging system") console.print() - console.print("[dim]Run 'python3 shutdown_module.py --help' for usage[/dim]") + console.print("[yellow]Usage:[/yellow]") + console.print(" drone @prax shutdown now") + console.print() + + console.print("[yellow]Subcommands:[/yellow]") + console.print(" now Shut down the PRAX logging system") + console.print() + + console.print("[yellow]Examples:[/yellow]") + console.print() + console.print(" [dim]# Shut down logging system[/dim]") + console.print(" $ drone @prax shutdown now") console.print() @@ -54,23 +63,35 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True - json_handler.log_operation("shutdown_command_executed", {"command": command}) - console.print("🛑 Shutting down PRAX logging system...") - shutdown_logging_system() - console.print("✅ PRAX logging system shutdown complete") + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + + if args[0] == 'now': + json_handler.log_operation("shutdown_command_executed", {"command": command}) + console.print("🛑 Shutting down PRAX logging system...") + shutdown_logging_system() + console.print("✅ PRAX logging system shutdown complete") + return True + + error(f"Unknown shutdown subcommand: {args[0]}") + print_help() return True def print_introspection(): - """Display module introspection info.""" + """Display module introspection - shows connected handlers""" console.print() - console.print("shutdown_module Module") - console.print("Implements the 'shutdown' command to cleanly stop PRAX logging system") + console.print("[bold cyan]PRAX Shutdown Module[/bold cyan]") console.print() - console.print("Connected Handlers:") - console.print(" modules/") - console.print(" - logger.py (shutdown_logging_system — stops watcher, restores logger, logs shutdown)") - console.print(" - logger.py (system_logger — auto-routing logger for module log files)") + console.print("[yellow]Connected Handlers:[/yellow]") + console.print() + + console.print(" [cyan]prax/modules/[/cyan]") + console.print(" [dim]- logger.py[/dim] (shutdown_logging_system, system_logger)") + console.print() + + console.print("[dim]Run 'drone @prax shutdown --help' for usage[/dim]") console.print() diff --git a/src/aipass/prax/apps/modules/status_module.py b/src/aipass/prax/apps/modules/status.py similarity index 96% rename from src/aipass/prax/apps/modules/status_module.py rename to src/aipass/prax/apps/modules/status.py index 4d4617bd..8eca4cbe 100755 --- a/src/aipass/prax/apps/modules/status_module.py +++ b/src/aipass/prax/apps/modules/status.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: status_module.py +# Name: status.py # Description: PRAX Status Command # Version: 1.1.0 # Created: 2025-11-15 @@ -45,10 +45,6 @@ def handle_command(command: str, args: List[str]) -> bool: if command != 'status': return False - if not args: - print_introspection() - return True - json_handler.log_operation("status_checked", {"subcommand": args[0] if args else "default"}) # --- sub-command routing ------------------------------------------------ @@ -110,7 +106,7 @@ def _handle_sync() -> bool: def print_introspection(): """Display module introspection info.""" console.print() - console.print("status_module Module") + console.print("status Module") console.print("Implements the 'status' command to display PRAX system status dashboard") console.print() console.print("Connected Handlers:") diff --git a/src/aipass/prax/apps/modules/terminal_module.py b/src/aipass/prax/apps/modules/terminal.py similarity index 91% rename from src/aipass/prax/apps/modules/terminal_module.py rename to src/aipass/prax/apps/modules/terminal.py index 4ee54a65..721928c0 100755 --- a/src/aipass/prax/apps/modules/terminal_module.py +++ b/src/aipass/prax/apps/modules/terminal.py @@ -1,5 +1,5 @@ # =================== AIPass ==================== -# Name: terminal_module.py +# Name: terminal.py # Description: PRAX Terminal Command # Version: 1.1.0 # Created: 2025-11-15 @@ -36,7 +36,7 @@ def print_introspection(): console.print(" [dim]- logger.py[/dim] (enable_terminal_output, disable_terminal_output, system_logger)") console.print() - console.print("[dim]Run 'python3 terminal_module.py --help' for usage[/dim]") + console.print("[dim]Run 'drone @prax terminal --help' for usage[/dim]") console.print() @@ -58,13 +58,10 @@ def print_help(): console.print("[yellow]Usage Examples:[/yellow]") console.print() console.print(" [dim]# Enable terminal output[/dim]") - console.print(" $ prax terminal enable") + console.print(" $ drone @prax terminal enable") console.print() console.print(" [dim]# Disable terminal output[/dim]") - console.print(" $ prax terminal disable") - console.print() - console.print(" [dim]# Standalone execution[/dim]") - console.print(" $ python3 terminal_module.py enable") + console.print(" $ drone @prax terminal disable") console.print() console.print("[dim]Commands: enable, disable[/dim]") @@ -89,6 +86,10 @@ def handle_command(command: str, args: List[str]) -> bool: print_introspection() return True + if args[0] in ('--help', '-h', 'help'): + print_help() + return True + try: if args[0] not in ['enable', 'disable']: print_help() diff --git a/src/aipass/prax/apps/prax.py b/src/aipass/prax/apps/prax.py index 2a0f7d53..666aeca3 100755 --- a/src/aipass/prax/apps/prax.py +++ b/src/aipass/prax/apps/prax.py @@ -96,7 +96,37 @@ def print_introspection(): console.print(f" [cyan]•[/cyan] {module_name}") console.print() - console.print("[dim]Run 'prax --help' for usage information[/dim]") + console.print("[dim]Run 'drone @prax --help' for usage information[/dim]") + console.print() + + +def print_help(): + """Rich-formatted top-level help for PRAX""" + console.print() + console.print("[bold cyan]PRAX - System-Wide Logging Infrastructure[/bold cyan]") + console.print() + + console.print("[yellow]Commands:[/yellow]") + console.print(" [cyan]monitor[/cyan] Mission Control - unified real-time monitoring") + console.print(" [cyan]init[/cyan] Initialize PRAX logging system") + console.print(" [cyan]status[/cyan] Show PRAX system status") + console.print(" [cyan]run[/cyan] Start continuous logging mode") + console.print(" [cyan]shutdown[/cyan] Shutdown PRAX logging system") + console.print(" [cyan]discover[/cyan] Discover Python modules in ecosystem") + console.print(" [cyan]terminal[/cyan] Enable/disable terminal output") + console.print() + + console.print("[yellow]Flags:[/yellow]") + console.print(" [cyan]--help, -h[/cyan] Show help information") + console.print(" [cyan]--version, -V[/cyan] Show version") + console.print(" [cyan]--verbose, -v[/cyan] Verbose output") + console.print() + + console.print("[yellow]Examples:[/yellow]") + console.print(" $ drone @prax monitor") + console.print(" $ drone @prax monitor run") + console.print(" $ drone @prax status") + console.print(" $ drone @prax terminal enable") console.print() @@ -135,6 +165,7 @@ def main(): parser = argparse.ArgumentParser( description='PRAX - System-Wide Logging Infrastructure', formatter_class=argparse.RawDescriptionHelpFormatter, + add_help=False, # We route --help to modules when a command is given epilog=""" Available Commands: monitor Mission Control - unified real-time monitoring @@ -146,10 +177,10 @@ Available Commands: terminal Enable/disable terminal output (requires: enable|disable) Examples: - prax monitor - prax init - prax status - prax terminal enable + drone @prax monitor + drone @prax init + drone @prax status + drone @prax terminal enable """ ) @@ -163,14 +194,19 @@ Examples: nargs='*', help='Arguments for the command') + parser.add_argument('--help', '-h', action='store_true', dest='show_help', + help='Show help information') parser.add_argument('--version', '-V', action='version', version='PRAX v2.0.0') parser.add_argument('--verbose', '-v', action='store_true', help='Verbose output') parsed_args, remaining = parser.parse_known_args() - # If no command provided, show introspection display + # If no command provided, show introspection or top-level help if not parsed_args.command: - print_introspection() + if parsed_args.show_help: + print_help() + else: + print_introspection() return 0 # Discover command modules @@ -184,6 +220,10 @@ Examples: # so subcommand handlers like dashboard can receive --all, --branch, etc. all_args = parsed_args.args + remaining + # Pass --help through to module handler (e.g. drone @prax monitor --help) + if parsed_args.show_help: + all_args = ['--help'] + all_args + # Route command to appropriate handler if route_command(parsed_args.command, all_args, handlers): return 0 diff --git a/src/aipass/prax/dev.local.md b/src/aipass/prax/dev.local.md deleted file mode 100644 index b076c93d..00000000 --- a/src/aipass/prax/dev.local.md +++ /dev/null @@ -1,15 +0,0 @@ -# dev.local.md - PRAX -``` -Branch: /home/coder/workspace/AIPass/src/aipass/prax -Created: 2026-03-07 -``` - -## Issues - -- - ---- - -## Todos - --