From 97952124b5e31a2cd351cf24efca2f8f890f0e39 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Sat, 14 Mar 2026 00:41:38 -0700 Subject: [PATCH] =?UTF-8?q?feat(system):=20stderr=20routing=20migration=20?= =?UTF-8?q?Phase=203=20=E2=80=94=2010=20branches=20migrated=20(FPLAN-0033)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrated error()/warning() calls across 10 branches to use CLI display API which routes to stderr. 48 files modified across ai_mail, api, backup, daemon, flow, memory, prax, seedgo, spawn, trigger. Seedgo stderr_routing standard created (24th standard) with full-file scanning. System average 49% → 69%. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/aipass/ai_mail/apps/ai_mail.py | 6 +- .../ai_mail/apps/modules/branch_ping.py | 8 +- src/aipass/ai_mail/apps/modules/dispatch.py | 6 +- src/aipass/ai_mail/apps/modules/email.py | 21 +- src/aipass/api/apps/api.py | 16 +- src/aipass/backup/apps/backup.py | 10 +- src/aipass/daemon/apps/daemon.py | 6 +- src/aipass/daemon/apps/modules/actions.py | 4 +- src/aipass/daemon/apps/modules/schedule.py | 6 +- src/aipass/daemon/apps/modules/update.py | 4 +- src/aipass/devpulse/dplan-003/README.md | 8 +- .../devpulse/dplan-003/credential_model.md | 2 + src/aipass/flow/CLOSED_PLANS.local.json | 14 + src/aipass/flow/apps/flow.py | 6 +- src/aipass/flow/apps/modules/close_plan.py | 8 +- src/aipass/flow/apps/modules/create_plan.py | 4 +- src/aipass/flow/apps/modules/dplan_flow.py | 10 +- .../apps/modules/dplan_post_close_runner.py | 6 +- src/aipass/flow/apps/modules/list_plans.py | 8 +- .../flow/apps/modules/post_close_runner.py | 6 +- .../flow/apps/modules/registry_monitor.py | 10 +- src/aipass/flow/apps/modules/restore_plan.py | 4 +- src/aipass/memory/apps/memory.py | 20 +- src/aipass/memory/apps/modules/rollover.py | 23 +- src/aipass/memory/apps/modules/search.py | 25 +- .../prax/apps/modules/agent_status_module.py | 4 +- src/aipass/prax/apps/modules/dashboard.py | 28 +- src/aipass/prax/apps/modules/init_module.py | 4 +- .../prax/apps/modules/log_audit_module.py | 4 +- .../prax/apps/modules/monitor_module.py | 2 +- .../prax/apps/modules/terminal_module.py | 4 +- src/aipass/prax/apps/prax.py | 10 +- .../aipass_standards/stderr_routing.md | 61 ++++ .../aipass_standards/stderr_routing_check.py | 303 ++++++++++++++++++ .../stderr_routing_content.py | 73 +++++ .../apps/handlers/audit/audit_display.py | 10 + .../apps/handlers/audit/branch_audit.py | 9 +- .../seedgo/apps/modules/standards_audit.py | 16 +- src/aipass/seedgo/apps/seedgo.py | 4 +- src/aipass/spawn/apps/modules/delete.py | 16 +- src/aipass/spawn/apps/modules/passport.py | 12 +- .../spawn/apps/modules/sync_registry.py | 10 +- .../spawn/apps/modules/sync_templates.py | 10 +- src/aipass/spawn/apps/modules/update.py | 18 +- src/aipass/spawn/apps/spawn.py | 31 +- src/aipass/trigger/apps/modules/core.py | 4 +- src/aipass/trigger/apps/modules/errors.py | 12 +- src/aipass/trigger/apps/trigger.py | 6 +- 48 files changed, 666 insertions(+), 226 deletions(-) create mode 100644 src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing.md create mode 100644 src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py create mode 100644 src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_content.py diff --git a/src/aipass/ai_mail/apps/ai_mail.py b/src/aipass/ai_mail/apps/ai_mail.py index 118138b9..8a9288bf 100644 --- a/src/aipass/ai_mail/apps/ai_mail.py +++ b/src/aipass/ai_mail/apps/ai_mail.py @@ -33,7 +33,7 @@ _UPDATE_SECTION = None # type: ignore from aipass.prax.apps.modules.logger import system_logger as logger # CLI services for display -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error # ============================================================================= # CONSTANTS & CONFIG @@ -228,14 +228,14 @@ def main(): remaining_args = args[1:] if len(args) > 1 else [] if not modules: - console.print("❌ ERROR: No modules found") + error("No modules found") return 1 # Route command if route_command(command, remaining_args, modules): return 0 else: - console.print(f"❌ ERROR: Unknown command: {command}") + error(f"Unknown command: {command}") return 1 if __name__ == "__main__": diff --git a/src/aipass/ai_mail/apps/modules/branch_ping.py b/src/aipass/ai_mail/apps/modules/branch_ping.py index 8dde2dbf..6db919d7 100644 --- a/src/aipass/ai_mail/apps/modules/branch_ping.py +++ b/src/aipass/ai_mail/apps/modules/branch_ping.py @@ -21,7 +21,7 @@ from typing import List from aipass.prax.apps.modules.logger import system_logger as logger # CLI services for formatting -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error from rich.panel import Panel # Import handlers @@ -65,7 +65,7 @@ def handle_ping(verbose: bool = False) -> bool: except Exception as e: logger.error(f"Ping failed: {e}") if verbose: - console.print(f"Ping failed: {e}") + error(f"Ping failed: {e}") return False @@ -88,7 +88,7 @@ def handle_status() -> bool: return True except Exception as e: logger.error(f"Status check failed: {e}") - console.print(f"Error getting status: {e}") + error(f"Error getting status: {e}") return False @@ -113,7 +113,7 @@ def handle_registry() -> bool: return True except Exception as e: logger.error(f"Registry read failed: {e}") - console.print(f"Error reading registry: {e}") + error(f"Error reading registry: {e}") return False diff --git a/src/aipass/ai_mail/apps/modules/dispatch.py b/src/aipass/ai_mail/apps/modules/dispatch.py index f06e72ca..c13186a1 100644 --- a/src/aipass/ai_mail/apps/modules/dispatch.py +++ b/src/aipass/ai_mail/apps/modules/dispatch.py @@ -18,7 +18,7 @@ from pathlib import Path from typing import List from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error from aipass.ai_mail.apps.handlers.dispatch.status import ( load_dispatch_log, check_pid_status, @@ -93,7 +93,7 @@ def handle_command(command: str, args: List[str]) -> bool: elif subcommand == "wake": return _orchestrate_wake(args[1:]) else: - console.print(f"[red]Unknown dispatch subcommand: {subcommand}[/red]") + error(f"Unknown dispatch subcommand: {subcommand}") print_help() return False @@ -175,7 +175,7 @@ def _orchestrate_wake(args: List[str]) -> bool: i += 1 if not filtered: - console.print("[red]Missing branch argument[/red]") + error("Missing branch argument") return False branch_email = filtered[0] diff --git a/src/aipass/ai_mail/apps/modules/email.py b/src/aipass/ai_mail/apps/modules/email.py index 707e8e0d..ed7aa37b 100644 --- a/src/aipass/ai_mail/apps/modules/email.py +++ b/src/aipass/ai_mail/apps/modules/email.py @@ -28,7 +28,7 @@ _AI_MAIL_DIR = Path(__file__).resolve().parents[2] _REPO_ROOT = _AI_MAIL_DIR.parents[2] from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, success from aipass.trigger.apps.modules.core import trigger # Handlers - business logic providers @@ -120,7 +120,7 @@ def handle_send(args: List[str]) -> bool: parsed = parse_send_args(args) if parsed["mode"] == "error": - console.print(f"[red]{parsed['error']}[/red]") + error(parsed['error']) console.print(" Multiple: send @branch1 @branch2 \"Subject\" \"Message\"") return False @@ -204,7 +204,7 @@ def _send_direct(to_branch, subject, message, auto_execute=False, pass return True else: - console.print(f"[red]Failed to deliver: {error_msg}[/red]") + error(f"Failed to deliver: {error_msg}") dispatch_send_error(to_branch, subject, error_msg, deliver_email_to_branch) return False except BrokenPipeError: @@ -212,7 +212,7 @@ def _send_direct(to_branch, subject, message, auto_execute=False, return True except Exception as e: logger.error(f"[email] Send failed: {e}") - console.print(f"[red]Error: {e}[/red]") + error(f"Error: {e}") dispatch_send_error(to_branch, subject, str(e), deliver_email_to_branch) return False @@ -226,10 +226,13 @@ def _send_broadcast(subject, message, user_info, auto_execute, no_memory_save, r branches, create_email_file, load_email_file, deliver_email_to_branch, _delivery_callback, log_operation, update_central) if isinstance(results, str): - console.print("[red]Failed to load email file for broadcast[/red]") + error("Failed to load email file for broadcast") return False - for name, success, err in results: - console.print(f" {'[green]OK[/green]' if success else '[red]FAIL[/red]'} {name}" + (f" ({err})" if not success else "")) + for name, ok, err in results: + if ok: + console.print(f" [green]OK[/green] {name}") + else: + error(f"FAIL {name} ({err})") console.print(f"\nBroadcast complete: {success_count}/{total} delivered") return ok @@ -241,7 +244,7 @@ def handle_inbox(args: List[str]) -> bool: first_arg = args[0] if args else None ok, info = resolve_inbox_target(first_arg, _REPO_ROOT, get_branch_by_email, get_current_user) if not ok: - console.print(f"[red]{info['error']}[/red]") + error(info['error']) return False inbox_file = info["inbox_file"] @@ -269,7 +272,7 @@ def handle_inbox(args: List[str]) -> bool: return True except Exception as e: logger.error(f"[email] Inbox view failed: {e}") - console.print(f"[red]Error: {e}[/red]") + error(f"Error: {e}") return False diff --git a/src/aipass/api/apps/api.py b/src/aipass/api/apps/api.py index 81d86adc..2afa81a9 100644 --- a/src/aipass/api/apps/api.py +++ b/src/aipass/api/apps/api.py @@ -25,7 +25,7 @@ from typing import Any, List from aipass.prax.apps.modules.logger import system_logger as logger # CLI services for formatted output -from aipass.cli.apps.modules import console, header +from aipass.cli.apps.modules import console, header, error from rich.panel import Panel from rich.table import Table from rich.columns import Columns @@ -102,9 +102,7 @@ def print_introspection(): modules = discover_modules() if not modules: - console.print("[red]No modules discovered[/red]") - console.print() - console.print("[dim]Run 'python3 api.py --help' for usage information[/dim]") + error("No modules discovered", suggestion="Run 'python3 api.py --help' for usage information") console.print() return @@ -254,9 +252,7 @@ def main(): if not modules: logger.error("No modules found") - console.print() - console.print("[red]ERROR: No modules found[/red]") - console.print() + error("No modules found") return 1 # Extract command and remaining args (matching seed pattern) @@ -274,11 +270,7 @@ def main(): return 0 else: logger.warning(f"Unknown command: {command}") - console.print() - console.print(f"[red]ERROR: Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]python3 api.py --help[/dim] for available commands") - console.print() + error(f"Unknown command: {command}", suggestion="Run 'python3 api.py --help' for available commands") return 1 if __name__ == "__main__": diff --git a/src/aipass/backup/apps/backup.py b/src/aipass/backup/apps/backup.py index 12e1ac06..6ea32c12 100755 --- a/src/aipass/backup/apps/backup.py +++ b/src/aipass/backup/apps/backup.py @@ -19,7 +19,7 @@ import argparse from pathlib import Path from typing import Any, List -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error from aipass.prax import logger # Explicit module imports (replaces dynamic discover_modules) @@ -172,7 +172,7 @@ def main(): modules = get_modules() if not modules: - console.print("❌ ERROR: No modules found") + error("No modules found") return 1 # 5. Handle 'all' command: snapshot → versioned → drive-sync @@ -187,7 +187,7 @@ def main(): force=args.force, test=False, limit=0 ) if not route_command(snapshot_args, modules): - console.print("[red]Snapshot failed - aborting[/red]") + error("Snapshot failed - aborting") return 1 console.print() @@ -201,7 +201,7 @@ def main(): force=args.force, test=False, limit=0 ) if not route_command(versioned_args, modules): - console.print("[red]Versioned backup failed - aborting[/red]") + error("Versioned backup failed - aborting") return 1 console.print() @@ -215,7 +215,7 @@ def main(): force=args.force, test=False, limit=args.limit ) if not route_command(sync_args, modules): - console.print("[red]Drive sync failed[/red]") + error("Drive sync failed") return 1 console.print() diff --git a/src/aipass/daemon/apps/daemon.py b/src/aipass/daemon/apps/daemon.py index 5c4443b3..2855efbd 100644 --- a/src/aipass/daemon/apps/daemon.py +++ b/src/aipass/daemon/apps/daemon.py @@ -22,7 +22,7 @@ from typing import List, Any from aipass.prax.apps.modules.logger import system_logger as logger # Console -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error def _header(text): console.print(f"\n[bold cyan]{'='*70}[/bold cyan]") @@ -186,9 +186,7 @@ def main(): return 0 else: console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]daemon --help[/dim] for available commands") + error(f"Unknown command: {command}", suggestion="Run 'daemon --help' for available commands") console.print() return 1 diff --git a/src/aipass/daemon/apps/modules/actions.py b/src/aipass/daemon/apps/modules/actions.py index 9fd16eaf..9b870724 100644 --- a/src/aipass/daemon/apps/modules/actions.py +++ b/src/aipass/daemon/apps/modules/actions.py @@ -20,7 +20,7 @@ from typing import List from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error as cli_error def _header(text): console.print(f"\n[bold cyan]{'='*70}[/bold cyan]") @@ -31,7 +31,7 @@ def _success(text): console.print(f"[green]OK:[/green] {text}") def _error(text): - console.print(f"[red]ERROR:[/red] {text}") + cli_error(text) from aipass.daemon.apps.handlers.actions.actions_registry import ( list_actions, diff --git a/src/aipass/daemon/apps/modules/schedule.py b/src/aipass/daemon/apps/modules/schedule.py index aaba0cb9..d49dd305 100644 --- a/src/aipass/daemon/apps/modules/schedule.py +++ b/src/aipass/daemon/apps/modules/schedule.py @@ -21,7 +21,7 @@ from typing import List from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error as cli_error def _header(text): console.print(f"\n[bold cyan]{'='*70}[/bold cyan]") @@ -32,7 +32,7 @@ def _success(text): console.print(f"[green]OK:[/green] {text}") def _error(text): - console.print(f"[red]ERROR:[/red] {text}") + cli_error(text) # Handler imports from aipass.daemon.apps.handlers.schedule.task_registry import ( @@ -178,7 +178,7 @@ def _handle_create(args: List[str]) -> bool: try: parsed = parser.parse_args(args) except SystemExit: - console.print("[red]Usage: schedule create \"task\" --due --to @branch [--message \"details\"][/red]") + _error('Usage: schedule create "task" --due --to @branch [--message "details"]') return False if not parsed.task: diff --git a/src/aipass/daemon/apps/modules/update.py b/src/aipass/daemon/apps/modules/update.py index 1b62cb12..d8c09ee0 100644 --- a/src/aipass/daemon/apps/modules/update.py +++ b/src/aipass/daemon/apps/modules/update.py @@ -19,7 +19,7 @@ from typing import Dict, Any, List from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error def _header(text): console.print(f"\n[bold cyan]{'='*70}[/bold cyan]") @@ -163,7 +163,7 @@ def handle_command(command: str, args: list) -> bool: except Exception as e: logger.error(f"[DAEMON] Error generating update digest: {e}", exc_info=True) - console.print(f"[red]Error: {e}[/red]") + error(f"Error: {e}") return True diff --git a/src/aipass/devpulse/dplan-003/README.md b/src/aipass/devpulse/dplan-003/README.md index 764c173b..dad7824b 100644 --- a/src/aipass/devpulse/dplan-003/README.md +++ b/src/aipass/devpulse/dplan-003/README.md @@ -16,7 +16,7 @@ Parent plan: `AIPass/DPLAN-003_aipass_as_operating_system_2026-03-13.md` |------|---------|--------| | `registry_discovery_map.md` | Every find_registry() call in src/aipass/ | Done | | `portability_audit.md` | Full investigation results (session 24) | Done | -| `credential_model.md` | Registry credential design (UUID now, macaroons later) | Active | -| `registry_refactor_plan.md` | Shared function design, migration steps | Pending | -| `aipass_init_spec.md` | What `aipass init` creates, CLI design, templates | Pending | -| `drone_help_spec.md` | `drone aipass help` module design | Pending | +| `credential_model.md` | Registry credential design (UUID now, macaroons later) | Stage 1 Done | +| `registry_refactor_plan.md` | Shared find_registry() design, migration steps | Pending | +| `aipass_init_spec.md` | CLI owns init — `drone @cli aipass init` (entry point later) | Pending | +| `aipass_help_spec.md` | CLI owns help — `drone @cli aipass help` (entry point later) | Pending | diff --git a/src/aipass/devpulse/dplan-003/credential_model.md b/src/aipass/devpulse/dplan-003/credential_model.md index 8bfb76ed..cac85438 100644 --- a/src/aipass/devpulse/dplan-003/credential_model.md +++ b/src/aipass/devpulse/dplan-003/credential_model.md @@ -152,3 +152,5 @@ Discovered during testing. Reference for future work. - **2026-03-14:** Drone verification added (`_verify_registry_credential` in load_registry). Drone dispatched and fixed error propagation — new `RegistryMismatchError` class separates "not found" (fallback OK) from "mismatch" (hard error). Spawn templates updated with `{{REGISTRY_ID}}` placeholder. - **2026-03-14:** Stage 1 complete. Credential model is end-to-end: init creates credentialed registries, drone verifies on load, spawn injects into new passports, mismatch = hard error. Remaining: drone CLI stderr surfacing (DPLAN-0031), `aipass init` CLI command (future). - **2026-03-14:** FPLAN-0032 executed — CLI stderr standardization Phase 1+2 done. CLI owns err_console, error()/warning() go to stderr. Drone imports from CLI instead of creating its own Console(stderr=True). Credential mismatch errors now properly surface to terminal via stderr. The stderr issue that blocked error visibility during DPLAN-003 testing is resolved. +- **2026-03-14:** CLI confirmed as owner of credential model + project commands. CLI owns: `aipass init` (create project), `aipass help` (project help), display API (error/warning/fatal/err_console). Drone owns verification (registry_handler). Spawn owns injection (passport templates). Access via `drone @cli aipass init/help` for now; `aipass init` entry point is future icing. +- **2026-03-14:** Seedgo `stderr_routing` standard created (24th standard). Checker bug found — was scanning all files but not displaying module/handler results. Seedgo fixed display + aggregation. Agent-verified across 3 branches. FPLAN-0033 parked for Phase 3 migration (343 error prints, 10 branches). diff --git a/src/aipass/flow/CLOSED_PLANS.local.json b/src/aipass/flow/CLOSED_PLANS.local.json index 8831c10c..3c223c40 100644 --- a/src/aipass/flow/CLOSED_PLANS.local.json +++ b/src/aipass/flow/CLOSED_PLANS.local.json @@ -27,6 +27,20 @@ "subject": "Build STATUS board — per-branch STATUS.local.md with Prax-managed central aggregation", "date_closed": "2026-03-10", "location": "flow" + }, + { + "plan_id": "FPLAN-0032", + "type": "FPLAN", + "subject": "CLI stderr standardization — Phase 1 add err_console to display.py", + "date_closed": "2026-03-14", + "location": "flow" + }, + { + "plan_id": "FPLAN-0031", + "type": "FPLAN", + "subject": "Drone stderr and error propagation — investigation and long-term fix", + "date_closed": "2026-03-14", + "location": "flow" } ] } diff --git a/src/aipass/flow/apps/flow.py b/src/aipass/flow/apps/flow.py index 75ae714c..09a2a4d6 100755 --- a/src/aipass/flow/apps/flow.py +++ b/src/aipass/flow/apps/flow.py @@ -32,7 +32,7 @@ signal.signal(signal.SIGPIPE, signal.SIG_DFL) from aipass.prax.apps.modules.logger import system_logger as logger # CLI services for formatted output -from aipass.cli.apps.modules import console, header, success, error +from aipass.cli.apps.modules import console, header, success, error, warning # ============================================================================= # MODULE DISCOVERY @@ -151,7 +151,7 @@ def main(): return 0 else: console.print() - console.print(f"[red]Unknown command: {command}[/red]") + error(f"Unknown command: {command}") console.print() console.print("Run [dim]drone @flow --help[/dim] for available commands") console.print() @@ -287,7 +287,7 @@ def print_module_help(command: str, modules: List[Any]): if not target_module: console.print() - console.print(f"[red]Unknown command: {command}[/red]") + error(f"Unknown command: {command}") console.print() console.print("Run [dim]drone @flow --help[/dim] for available commands") console.print() diff --git a/src/aipass/flow/apps/modules/close_plan.py b/src/aipass/flow/apps/modules/close_plan.py index 6a5faae8..957d4bce 100644 --- a/src/aipass/flow/apps/modules/close_plan.py +++ b/src/aipass/flow/apps/modules/close_plan.py @@ -34,7 +34,7 @@ from aipass.prax import logger from aipass.flow.apps.handlers.json import json_handler # CLI services for display and error handling -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning # Internal: Registry handlers from aipass.flow.apps.handlers.registry.load_registry import load_registry @@ -90,7 +90,7 @@ def _display_messages(messages: List[Dict[str, Any]]): console.print(format_plan_error(error_text, plan_num, details=details)) elif msg_type == "warning": - console.print(f"[yellow]{msg['text']}[/yellow]") + warning(msg['text']) elif msg_type == "dim": console.print(f"[dim]{msg['text']}[/dim]") @@ -102,7 +102,7 @@ def _display_messages(messages: List[Dict[str, Any]]): console.print(f"[green]{msg['text']}[/green]") elif msg_type == "error_text": - console.print(f"[red]{msg['text']}[/red]") + error(msg['text']) elif msg_type == "header": console.print(format_plan_deletion_header(msg["plan_key"], msg["plan_info"])) @@ -119,7 +119,7 @@ def _display_messages(messages: List[Dict[str, Any]]): console.print(f" * FPLAN-{plan['plan_num']}: {plan['subject']}") elif msg_type == "confirm_warning": - console.print(f"\n[bold red]WARNING: This will close all {msg['count']} plans![/bold red]") + error(f"WARNING: This will close all {msg['count']} plans!") elif msg_type == "closing_all": console.print(f"\n[bold]Closing all {msg['count']} plan(s)...[/bold]") diff --git a/src/aipass/flow/apps/modules/create_plan.py b/src/aipass/flow/apps/modules/create_plan.py index 90200ba3..2ae0add8 100755 --- a/src/aipass/flow/apps/modules/create_plan.py +++ b/src/aipass/flow/apps/modules/create_plan.py @@ -44,7 +44,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # CLI services for display -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, warning # Registry handlers (cross-domain - OK for modules) from aipass.flow.apps.handlers.registry.load_registry import load_registry @@ -195,7 +195,7 @@ def create_plan( if msg_type == "dim": console.print(f"[dim]{msg['text']}[/dim]") elif msg_type == "warning": - console.print(f"[yellow]{msg['text']}[/yellow]") + warning(msg['text']) elif msg_type == "display": console.print(msg["text"]) return ok, num, loc, tmpl, error diff --git a/src/aipass/flow/apps/modules/dplan_flow.py b/src/aipass/flow/apps/modules/dplan_flow.py index b3b4b27a..7f196bdc 100644 --- a/src/aipass/flow/apps/modules/dplan_flow.py +++ b/src/aipass/flow/apps/modules/dplan_flow.py @@ -20,7 +20,7 @@ from typing import List # Infrastructure imports (module does the logging) from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console, header, success, error +from aipass.cli.apps.modules import console, header, success, error, warning # Handler imports (local handlers in handlers/dplan/) from aipass.flow.apps.handlers.dplan.create import create_plan @@ -458,7 +458,7 @@ def _handle_close(args: List[str]) -> bool: console.print(f"[dim] Memory Bank archival running in background[/dim]") except Exception as e: logger.warning(f"[dev_flow] Failed to spawn background processing: {e}") - console.print(f"[yellow] Background archival failed to start - will retry on next close[/yellow]") + warning("Background archival failed to start - will retry on next close") # Step 3/3: Done console.print(f"[dim][3/3][/dim] Finalizing...") @@ -476,7 +476,7 @@ def _handle_close_all() -> bool: open_plans = get_open_plans() if not open_plans: - console.print("\n[yellow]No open plans to close[/yellow]\n") + warning("No open plans to close") return True console.print(f"\n[bold yellow]Found {len(open_plans)} open plan(s) to close:[/bold yellow]") @@ -511,7 +511,7 @@ def _handle_close_all() -> bool: else: failure_count += 1 logger.warning(f"[dev_flow] Failed to close DPLAN-{p['number']:03d}: {err}") - console.print(f"[red] Failed: {err}[/red]") + error(f" Failed: {err}") # Push dashboard try: @@ -530,7 +530,7 @@ def _handle_close_all() -> bool: console.print(f"\n[dim]Background processing started for {success_count} plan(s)[/dim]") except Exception as e: logger.warning(f"[dev_flow] Failed to spawn background processing: {e}") - console.print(f"\n[yellow]Background processing failed to start[/yellow]") + warning("Background processing failed to start") console.print("\n" + "=" * 60) console.print("[bold green]CLOSE ALL COMPLETE[/bold green]") diff --git a/src/aipass/flow/apps/modules/dplan_post_close_runner.py b/src/aipass/flow/apps/modules/dplan_post_close_runner.py index d446b712..7ef68daf 100644 --- a/src/aipass/flow/apps/modules/dplan_post_close_runner.py +++ b/src/aipass/flow/apps/modules/dplan_post_close_runner.py @@ -25,7 +25,7 @@ from pathlib import Path FLOW_ROOT = Path(__file__).resolve().parents[2] # External: CLI console (Rich display) and Prax logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.prax.apps.modules.logger import system_logger as logger MODULE_NAME = "dplan_post_close_runner" @@ -58,7 +58,7 @@ def handle_command(command: str, args: list) -> bool: # Run the post-close processing directly (foreground) if not _acquire_lock(): - console.print("[yellow]Another instance is already running[/yellow]") + warning("Another instance is already running") return True try: @@ -66,7 +66,7 @@ def handle_command(command: str, args: list) -> bool: console.print(f"[green]Processing complete:[/green] {result.get('processed', 0)} processed, {result.get('errors', 0)} errors") except Exception as e: logger.error(f"[{MODULE_NAME}] Background processing failed: {e}") - console.print(f"[red]Processing failed: {e}[/red]") + error(f"Processing failed: {e}") finally: _release_lock() diff --git a/src/aipass/flow/apps/modules/list_plans.py b/src/aipass/flow/apps/modules/list_plans.py index cfb6dfa4..a5ffdc12 100755 --- a/src/aipass/flow/apps/modules/list_plans.py +++ b/src/aipass/flow/apps/modules/list_plans.py @@ -43,7 +43,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # CLI services for display -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning # Registry handlers from aipass.flow.apps.handlers.registry.load_registry import load_registry @@ -149,13 +149,13 @@ def list_plans(filter_type: str = "open") -> bool: # Module handles display if result.get("empty") and result.get("success"): - console.print("[yellow]No plans found in registry[/yellow]") + warning("No plans found in registry") return True if not result.get("success"): error_msg = result.get("error", "Unknown error") try: - console.print(f"[red]ERROR: {error_msg}[/red]") + error(f"ERROR: {error_msg}") except BrokenPipeError: pass return False @@ -204,7 +204,7 @@ def handle_command(command: str, args: List[str]) -> bool: if filter_arg in ["open", "closed", "all"]: filter_type = filter_arg else: - console.print(f"[yellow]Unknown filter '{filter_arg}', defaulting to 'open'[/yellow]") + warning(f"Unknown filter '{filter_arg}', defaulting to 'open'") console.print("[dim]Valid filters: open, closed, all[/dim]") # STEP 2: Execute workflow diff --git a/src/aipass/flow/apps/modules/post_close_runner.py b/src/aipass/flow/apps/modules/post_close_runner.py index 44b0ea5d..f2e0b184 100644 --- a/src/aipass/flow/apps/modules/post_close_runner.py +++ b/src/aipass/flow/apps/modules/post_close_runner.py @@ -28,7 +28,7 @@ _PKG_ROOT = Path(__file__).resolve().parents[3] # file.py → modules/ → apps FLOW_ROOT = _PKG_ROOT / "flow" # External: CLI console (Rich display) and Prax logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.prax.apps.modules.logger import system_logger as logger MODULE_NAME = "post_close_runner" @@ -63,7 +63,7 @@ def handle_command(command: str, args: list) -> bool: # Run the post-close processing directly (foreground) if not _acquire_lock(): - console.print("[yellow]Another instance is already running[/yellow]") + warning("Another instance is already running") return True try: @@ -71,7 +71,7 @@ def handle_command(command: str, args: list) -> bool: console.print("[green]Processing complete[/green]") except Exception as e: logger.error(f"[{MODULE_NAME}] Background processing failed: {e}") - console.print(f"[red]Processing failed: {e}[/red]") + error(f"Processing failed: {e}") finally: _release_lock() diff --git a/src/aipass/flow/apps/modules/registry_monitor.py b/src/aipass/flow/apps/modules/registry_monitor.py index 019e8827..aea73df1 100644 --- a/src/aipass/flow/apps/modules/registry_monitor.py +++ b/src/aipass/flow/apps/modules/registry_monitor.py @@ -53,7 +53,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # CLI services for display -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning # Registry handlers from aipass.flow.apps.handlers.registry.load_registry import load_registry @@ -115,11 +115,11 @@ def start_monitoring(): # Module handles display status = result.get("status", "") if status == "already_running": - console.print("[yellow]Monitor is already running[/yellow]") + warning("Monitor is already running") elif status == "started": console.print(f"[green]OK[/green] {result['message']}") elif status == "error": - console.print(f"[red]{result['message']}[/red]") + error(result['message']) return result.get("success", False) @@ -135,7 +135,7 @@ def stop_monitoring(): if status == "stopped": console.print("[green]OK[/green] Monitor stopped") elif status == "not_running": - console.print("[yellow]Monitor is not running[/yellow]") + warning("Monitor is not running") return result.get("success", False) @@ -252,7 +252,7 @@ def handle_command(command: str, args: List[str]) -> bool: return True else: - console.print(f"[red]Unknown subcommand: {subcommand}[/red]") + error(f"Unknown subcommand: {subcommand}") console.print() console.print("Available commands:") console.print(" • scan - One-time scan and heal registry") diff --git a/src/aipass/flow/apps/modules/restore_plan.py b/src/aipass/flow/apps/modules/restore_plan.py index b55732e7..1de28a87 100644 --- a/src/aipass/flow/apps/modules/restore_plan.py +++ b/src/aipass/flow/apps/modules/restore_plan.py @@ -33,7 +33,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # CLI services for display and error handling -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, warning # Internal: Registry handlers from aipass.flow.apps.handlers.registry.load_registry import load_registry @@ -84,7 +84,7 @@ def _display_messages(messages: List[Dict[str, Any]]): console.print(format_restore_error(error_type, plan_key, details=details)) elif msg_type == "warning": - console.print(f"[yellow]{msg['text']}[/yellow]") + warning(msg['text']) elif msg_type == "dim": console.print(f"[dim]{msg['text']}[/dim]") diff --git a/src/aipass/memory/apps/memory.py b/src/aipass/memory/apps/memory.py index 685a003a..917eab60 100755 --- a/src/aipass/memory/apps/memory.py +++ b/src/aipass/memory/apps/memory.py @@ -30,7 +30,7 @@ from rich import box from rich.table import Table from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error # ============================================================================= # INFRASTRUCTURE SETUP @@ -55,7 +55,7 @@ def print_introspection(): # Discover modules modules = discover_modules() - console.print(f"[yellow]Discovered Modules:[/yellow] {len(modules)}") + console.print(f"[bold cyan]Discovered Modules:[/bold cyan] {len(modules)}") console.print() for module in modules: @@ -123,12 +123,12 @@ def print_help(): console.print("[bold cyan]USAGE:[/bold cyan]") console.print() - console.print(" [yellow]Via Drone (recommended):[/yellow]") - console.print(" [dim]drone @memory search \"error handling\"[/dim]") + console.print(" [bold]Via Drone (recommended):[/bold]") + console.print(" [dim]drone @memory search \"performance patterns\"[/dim]") console.print(" [dim]drone @memory status[/dim]") console.print(" [dim]drone @memory rollover[/dim]") console.print() - console.print(" [yellow]Direct execution:[/yellow]") + console.print(" [bold]Direct execution:[/bold]") console.print(" [dim]python3 -m aipass.memory.apps.memory search \"query\"[/dim]") console.print(" [dim]python3 -m aipass.memory.apps.memory rollover[/dim]") console.print() @@ -141,7 +141,7 @@ def print_help(): console.print(" [cyan]--type TYPE[/cyan] Filter by memory type (observations, local)") console.print(" [cyan]--n N[/cyan] Number of results (default: 5)") console.print() - console.print(" [yellow]Example:[/yellow]") + console.print(" [bold]Example:[/bold]") console.print(" [dim]drone @memory search \"registry bugs\" --branch SEED --n 10[/dim]") console.print() console.print("-" * 70) @@ -149,7 +149,7 @@ def print_help(): console.print("[bold cyan]WATCH MODE:[/bold cyan]") console.print() - console.print(" [yellow]Start memory file watcher:[/yellow]") + console.print(" [bold]Start memory file watcher:[/bold]") console.print(" [dim]drone @memory watch[/dim]") console.print(" [dim]Monitors all branches, auto-rolls when limit exceeded[/dim]") console.print(" [dim]Press Ctrl+C to stop[/dim]") @@ -275,7 +275,7 @@ def start_watch() -> None: result = start_memory_watcher() if not result.get('success'): - console.print(f"[red]x Failed to start watcher: {result.get('error')}[/red]") + error(f"Failed to start watcher: {result.get('error')}") return console.print(f"[green]>[/green] Watching {result.get('count', 0)} branch directories") @@ -332,9 +332,7 @@ def main(): return # Module handled it successfully else: console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]python3 -m aipass.memory.apps.memory --help[/dim] for available commands") + error(f"Unknown command: {command}", suggestion="Run 'python3 -m aipass.memory.apps.memory --help' for available commands") console.print() return diff --git a/src/aipass/memory/apps/modules/rollover.py b/src/aipass/memory/apps/modules/rollover.py index 01ca653c..61514224 100755 --- a/src/aipass/memory/apps/modules/rollover.py +++ b/src/aipass/memory/apps/modules/rollover.py @@ -27,7 +27,7 @@ from rich.panel import Panel from rich import box from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning # ============================================================================= # INFRASTRUCTURE SETUP @@ -139,7 +139,7 @@ def run_rollover() -> bool: result = _handler_execute_rollover() if not result.get('success') and result.get('error'): - console.print(f"[red]x[/red] {result['error']}") + error(result['error']) return False triggers_count = result.get('triggers_count', 0) @@ -168,9 +168,8 @@ def run_rollover() -> bool: if failed: console.print() - console.print("[red]Failed operations:[/red]") for fail in failed: - console.print(f" [red]x[/red] {fail['trigger']} - {fail['stage']}: {fail['error']}") + error(f"{fail['trigger']} - {fail['stage']}: {fail['error']}") return success_count > 0 @@ -201,11 +200,11 @@ def sync_line_counts() -> None: if result['success']: console.print(f"[green]>[/green] Updated {result['updated']} files") if result['failed'] > 0: - console.print(f"[yellow]![/yellow] {result['failed']} files failed:") - for branch, mem_type, error in result.get('failures', []): - console.print(f" [red]x[/red] {branch}.{mem_type}: {error}") + warning(f"{result['failed']} files failed") + for branch, mem_type, err_msg in result.get('failures', []): + error(f"{branch}.{mem_type}: {err_msg}") else: - console.print("[red]x[/red] Failed to sync line counts") + error("Failed to sync line counts") console.print() @@ -235,7 +234,7 @@ def show_status() -> None: stats_result = detector.get_rollover_stats() if not stats_result['success']: - console.print(f"[red]x[/red] Failed to get status: {stats_result.get('error', 'Unknown error')}") + error(f"Failed to get status: {stats_result.get('error', 'Unknown error')}") logger.error(f"[rollover] Failed to get status: {stats_result.get('error')}") return @@ -249,7 +248,7 @@ def show_status() -> None: # Per-branch details if stats['branches']: - console.print("[yellow]Branch Details:[/yellow]") + console.print("[bold cyan]Branch Details:[/bold cyan]") console.print() for branch_name, branch_stats in stats['branches'].items(): @@ -294,7 +293,7 @@ def check_triggers() -> None: triggers_result = detector.check_all_branches() if not triggers_result['success']: - console.print(f"[red]x[/red] Failed to check triggers: {triggers_result.get('error', 'Unknown error')}") + error(f"Failed to check triggers: {triggers_result.get('error', 'Unknown error')}") logger.error(f"[rollover] Failed to check triggers: {triggers_result.get('error')}") return @@ -304,7 +303,7 @@ def check_triggers() -> None: console.print("[green]>[/green] No files need rollover") return - console.print(f"[yellow]Found {len(triggers)} files ready for rollover:[/yellow]") + console.print(f"[bold cyan]Found {len(triggers)} files ready for rollover:[/bold cyan]") console.print() for trigger in triggers: diff --git a/src/aipass/memory/apps/modules/search.py b/src/aipass/memory/apps/modules/search.py index 61270db3..75e70ad0 100755 --- a/src/aipass/memory/apps/modules/search.py +++ b/src/aipass/memory/apps/modules/search.py @@ -26,7 +26,7 @@ from rich.panel import Panel from rich import box from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning # ============================================================================= # INFRASTRUCTURE SETUP @@ -63,8 +63,7 @@ def handle_command(command: str, args: List[str]) -> bool: if command == 'search': if not args: - console.print("[red]Error:[/red] Search query required") - console.print("Usage: search [--branch BRANCH] [--type TYPE] [--n N]") + error("Search query required", suggestion="Usage: search [--branch BRANCH] [--type TYPE] [--n N]") return True # Parse arguments @@ -85,7 +84,7 @@ def handle_command(command: str, args: List[str]) -> bool: try: n_results = int(args[i + 1]) except ValueError: - console.print(f"[red]Error:[/red] Invalid number: {args[i + 1]}") + error(f"Invalid number: {args[i + 1]}") return True i += 2 else: @@ -94,7 +93,7 @@ def handle_command(command: str, args: List[str]) -> bool: query = ' '.join(query_parts) if not query: - console.print("[red]Error:[/red] Search query required") + error("Search query required") return True show_search_results(query, branch=branch, memory_type=memory_type, n_results=n_results) @@ -126,7 +125,7 @@ def print_help() -> None: console.print() console.print("[bold]EXAMPLES:[/bold]") console.print(" # Search all branches") - console.print(" [dim]drone @memory search \"error handling patterns\"[/dim]") + console.print(" [dim]drone @memory search \"performance patterns\"[/dim]") console.print() console.print(" # Search specific branch") console.print(" [dim]drone @memory search \"registry bugs\" --branch SEED[/dim]") @@ -191,7 +190,7 @@ def show_search_results( ) if not result['success']: - console.print(f"[red]x[/red] {result.get('error', 'Unknown error')}") + error(result.get('error', 'Unknown error')) return False collections_searched = result.get('collections_searched', 0) @@ -203,19 +202,11 @@ def show_search_results( console.print() if not filtered_results and total_results == 0: - console.print("[yellow]No matching memories found[/yellow]") - console.print() - console.print("[dim]Try:[/dim]") - console.print(" * Different search terms") - console.print(" * Broader query without filters") - console.print(" * Check if memories have been rolled over (drone @memory status)") + warning("No matching memories found", details="Try different search terms, broader query without filters, or check if memories have been rolled over (drone @memory status)") return True if not filtered_results: - console.print("[yellow]No relevant memories found[/yellow]") - console.print() - console.print("[dim]The search found some results but none were relevant enough (>40% similarity).[/dim]") - console.print("[dim]Try more specific search terms related to your AIPass work.[/dim]") + warning("No relevant memories found", details="Results found but none relevant enough (>40% similarity). Try more specific search terms.") return True for i, item in enumerate(filtered_results, 1): diff --git a/src/aipass/prax/apps/modules/agent_status_module.py b/src/aipass/prax/apps/modules/agent_status_module.py index f9402b81..561adae9 100644 --- a/src/aipass/prax/apps/modules/agent_status_module.py +++ b/src/aipass/prax/apps/modules/agent_status_module.py @@ -17,7 +17,7 @@ import sys from typing import List from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error def print_introspection(): @@ -102,7 +102,7 @@ def handle_command(command: str, args: List[str]) -> bool: if result: console.print("[green]✅ Pushed to all branch dashboards[/green]\n") else: - console.print("[red]❌ Push failed — check logs[/red]\n") + error("Push failed — check logs") return True diff --git a/src/aipass/prax/apps/modules/dashboard.py b/src/aipass/prax/apps/modules/dashboard.py index fff009c2..3d9ef1f6 100644 --- a/src/aipass/prax/apps/modules/dashboard.py +++ b/src/aipass/prax/apps/modules/dashboard.py @@ -21,7 +21,7 @@ from pathlib import Path from typing import Dict, List from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning # Import handlers from aipass.prax.apps.handlers.dashboard.operations import ( @@ -192,7 +192,7 @@ def print_status(): try: branches = get_branch_paths() except Exception as e: - console.print(f"[red]Error loading branches: {e}[/red]") + error(f"Error loading branches: {e}") return console.print() @@ -254,13 +254,13 @@ def _handle_refresh(args: List[str]) -> None: if result["status"] == "success": console.print(f"[green]Refreshed {result['branches_updated']} branches[/green]") elif result["status"] == "partial": - console.print(f"[yellow]Refreshed {result['branches_updated']} branches, {result['branches_failed']} failed[/yellow]") + warning(f"Refreshed {result['branches_updated']} branches, {result['branches_failed']} failed") for err in result.get("errors", []): - console.print(f" [red]{err}[/red]") + error(str(err)) else: - console.print(f"[red]Refresh failed[/red]") + error("Refresh failed") for err in result.get("errors", []): - console.print(f" [red]{err}[/red]") + error(str(err)) return # @branch arg: refresh specific branch @@ -268,14 +268,14 @@ def _handle_refresh(args: List[str]) -> None: try: branch_path = _resolve_branch_path(args[0]) except FileNotFoundError as e: - console.print(f"[red]{e}[/red]") + error(str(e)) return console.print(f"[dim]Refreshing {branch_path.name.upper()} dashboard...[/dim]") result = refresh_single_dashboard(branch_path) if result["status"] == "success": console.print(f"[green]Refreshed {result['branch']}[/green]") else: - console.print(f"[red]Failed: {result.get('error', 'unknown')}[/red]") + error(f"Failed: {result.get('error', 'unknown')}") return # No args: refresh current branch (detect from CWD) @@ -296,7 +296,7 @@ def _handle_refresh(args: List[str]) -> None: if result["status"] == "success": console.print(f"[green]Refreshed {result['branch']}[/green]") else: - console.print(f"[red]Failed: {result.get('error', 'unknown')}[/red]") + error(f"Failed: {result.get('error', 'unknown')}") def _handle_push_template(args: List[str]) -> None: @@ -334,9 +334,9 @@ def _handle_push_template(args: List[str]) -> None: if result["errors"]: console.print() - console.print(f"[red]Errors ({len(result['errors'])}):[/red]") + error(f"Errors ({len(result['errors'])})") for err in result["errors"]: - console.print(f" [red]! {err}[/red]") + error(f"! {err}") if not result["changes"] and not result["errors"]: console.print() @@ -362,13 +362,13 @@ def _handle_diff_template(args: List[str]) -> None: if idx + 1 < len(args): branch_name = args[idx + 1] else: - console.print("[red]--branch requires a branch name[/red]") + error("--branch requires a branch name") return result = diff_dashboard_template(branch_name=branch_name) if "error" in result: - console.print(f"[red]Error: {result['error']}[/red]") + error(result['error']) return summary = result.get("summary", {}) @@ -491,7 +491,7 @@ def main(): remaining_args = args[1:] if not handle_command(command, remaining_args): - console.print(f"[red]Unknown command: {command}[/red]") + error(f"Unknown command: {command}") print_help() diff --git a/src/aipass/prax/apps/modules/init_module.py b/src/aipass/prax/apps/modules/init_module.py index b25ebda3..d023638b 100755 --- a/src/aipass/prax/apps/modules/init_module.py +++ b/src/aipass/prax/apps/modules/init_module.py @@ -16,7 +16,7 @@ import sys from typing import List from aipass.prax.apps.modules.logger import initialize_logging_system, system_logger as logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error def print_introspection(): @@ -77,7 +77,7 @@ def handle_command(command: str, args: List[str]) -> bool: except Exception as e: logger.error(f"Error in init command: {e}") - console.print(f"[red]❌ ERROR: {e}[/red]") + error(str(e)) return True diff --git a/src/aipass/prax/apps/modules/log_audit_module.py b/src/aipass/prax/apps/modules/log_audit_module.py index 877d4c14..3d0e1689 100644 --- a/src/aipass/prax/apps/modules/log_audit_module.py +++ b/src/aipass/prax/apps/modules/log_audit_module.py @@ -18,7 +18,7 @@ import sys from typing import List from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error def print_introspection(): @@ -71,7 +71,7 @@ def _display_audit(files: list, summary: dict) -> None: if summary['healthy']: console.print("[green] Status: HEALTHY — all logs within limits[/green]") else: - console.print(f"[red] Status: {summary['oversized_count']} oversized, {summary['critical_count']} critical[/red]") + error(f"Status: {summary['oversized_count']} oversized, {summary['critical_count']} critical") # Show oversized files oversized = [f for f in files if f["status"] != "ok"] diff --git a/src/aipass/prax/apps/modules/monitor_module.py b/src/aipass/prax/apps/modules/monitor_module.py index 46035f8b..1c21fc95 100755 --- a/src/aipass/prax/apps/modules/monitor_module.py +++ b/src/aipass/prax/apps/modules/monitor_module.py @@ -389,7 +389,7 @@ def _interactive_loop(): elif cmd == 'status': _print_status() else: - console.print(f"[red]Unknown command: {cmd}[/red]") + error(f"Unknown command: {cmd}") console.print("[dim]Type 'help' for available commands[/dim]") except KeyboardInterrupt: diff --git a/src/aipass/prax/apps/modules/terminal_module.py b/src/aipass/prax/apps/modules/terminal_module.py index 78daa72f..7a4a8130 100755 --- a/src/aipass/prax/apps/modules/terminal_module.py +++ b/src/aipass/prax/apps/modules/terminal_module.py @@ -17,7 +17,7 @@ from pathlib import Path from typing import List from aipass.prax.apps.modules.logger import enable_terminal_output, disable_terminal_output, system_logger as logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error def print_introspection(): @@ -101,7 +101,7 @@ def handle_command(command: str, args: List[str]) -> bool: except Exception as e: logger.error(f"Error in terminal command: {e}") - console.print(f"[red]❌ ERROR: {e}[/red]") + error(str(e)) return True diff --git a/src/aipass/prax/apps/prax.py b/src/aipass/prax/apps/prax.py index baf22b7b..a9c6fc69 100755 --- a/src/aipass/prax/apps/prax.py +++ b/src/aipass/prax/apps/prax.py @@ -25,7 +25,7 @@ from typing import List, Callable from aipass.prax.apps.modules.logger import system_logger as logger # CLI services -from aipass.cli.apps.modules import console, header, success, error +from aipass.cli.apps.modules import console, header, success, error, warning # ============================================================================= # MODULE DISCOVERY @@ -63,7 +63,7 @@ def discover_command_modules() -> List[Callable]: command_handlers.append(module.handle_command) except Exception as e: - console.print(f"Warning: Failed to load module {module_file.name}: {e}") + warning(f"Failed to load module {module_file.name}: {e}") return command_handlers @@ -121,7 +121,7 @@ def route_command(command: str, args: List[str], handlers: List[Callable]) -> bo if handler(command, args): return True except Exception as e: - console.print(f"❌ ERROR: Handler failed: {e}") + error(f"Handler failed: {e}") return False return False @@ -177,14 +177,14 @@ Examples: handlers = discover_command_modules() if not handlers: - console.print("❌ ERROR: No command modules discovered") + error("No command modules discovered") return 1 # Route command to appropriate handler if route_command(parsed_args.command, parsed_args.args, handlers): return 0 else: - console.print(f"❌ ERROR: Unknown command: {parsed_args.command}") + error(f"Unknown command: {parsed_args.command}") return 1 if __name__ == "__main__": diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing.md b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing.md new file mode 100644 index 00000000..149c405d --- /dev/null +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing.md @@ -0,0 +1,61 @@ +# Stderr Routing Standard +**Status:** Active +**Date:** 2026-03-13 +**Origin:** FPLAN-0032 (CLI stderr standardization) + +## What This Covers + +Error and warning output must route to stderr via CLI display functions, not stay on stdout via raw `console.print()` with colored markup. + +--- + +## The Problem + +CLI `display.py` now has `err_console = Console(stderr=True)`. The functions `error()`, `warning()`, and `fatal()` route through it automatically. But branches still using `console.print('[red]Error...[/red]')` send errors to stdout — breaking piping, redirection, and machine-readable output. + +## Correct Pattern + +```python +from aipass.cli.apps.modules import error, warning, fatal + +error('Branch not found', suggestion='Check spelling') # stderr +warning('Template mismatch', details='Expected v2') # stderr +fatal('Config missing') # stderr + sys.exit(1) +``` + +## Wrong Pattern + +```python +console.print('[red]Error: Branch not found[/red]') # stdout! +console.print('[yellow]Warning: mismatch[/yellow]') # stdout! +Console(stderr=True) # Don't create your own +``` + +## What the Checker Catches + +1. `console.print()` with `[red]`/`[bold red]` markup — use `error()` or `fatal()` +2. `console.print()` with `[yellow]`/`[bold yellow]` markup — use `warning()` +3. Custom `Console(stderr=True)` creation — import `err_console` from CLI + +## CLI Display Exports + +```python +from aipass.cli.apps.modules import ( + error, # ❌ message + optional suggestion → stderr + warning, # ⚠ message + optional details → stderr + fatal, # ❌ message + sys.exit(1) → stderr + err_console, # Raw stderr Console (rare, prefer functions above) +) +``` + +## Scope + +- All branches except CLI (which defines these functions) +- Handlers are exempt (CLI separation standard covers them separately) +- `if __name__ == '__main__':` blocks are excluded + +## Reference + +- `src/aipass/cli/apps/modules/display.py` — error(), warning(), fatal() definitions +- FPLAN-0032 — Phase 1+2: CLI stderr standardization (closed) +- FPLAN-0033 — Phase 3: branch migration (gates on this standard) diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py new file mode 100644 index 00000000..affb0fe1 --- /dev/null +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py @@ -0,0 +1,303 @@ +# =================== AIPass ==================== +# Name: stderr_routing_check.py +# Description: Stderr Routing Standards Checker +# Version: 1.0.0 +# Created: 2026-03-13 +# Modified: 2026-03-13 +# ============================================= + +""" +Stderr Routing Standards Checker + +Validates that error/warning output uses CLI display functions (error(), warning(), +fatal()) which route to stderr, instead of raw console.print() with red/yellow markup +that stays on stdout. + +CORRECT: + from aipass.cli.apps.modules import error, warning, fatal + error('Branch not found', suggestion='Check spelling') + warning('Template mismatch', details='Expected v2') + +WRONG: + console.print('[red]Error: Branch not found[/red]') + console.print('[yellow]Warning: mismatch[/yellow]') +""" + +import re +from pathlib import Path +from typing import Dict, List + + +AUDIT_SCOPE = "all_files" + + +def is_bypassed(file_path: str, standard: str, line: int | None = None, bypass_rules: list | None = None) -> bool: + """Check if a violation should be bypassed.""" + if not bypass_rules: + return False + for rule in bypass_rules: + if rule.get('standard') and rule.get('standard') != standard: + continue + rule_file = rule.get('file', '') + if rule_file and rule_file not in file_path: + continue + rule_lines = rule.get('lines', []) + if rule_lines and line is not None: + if line in rule_lines: + return True + elif not rule_lines: + return True + return False + + +def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: + """ + Check if module routes error/warning output to stderr via CLI display functions. + + Returns: + dict: {passed, checks, score, standard} + """ + checks: List[Dict] = [] + path = Path(module_path) + + if is_bypassed(module_path, 'stderr_routing', bypass_rules=bypass_rules): + return { + 'passed': True, + 'checks': [{'name': 'Bypassed', 'passed': True, 'message': 'Standard bypassed via .seedgo/bypass.json'}], + 'score': 100, + 'standard': 'STDERR_ROUTING' + } + + if not path.exists(): + return { + 'passed': False, + 'checks': [{'name': 'File exists', 'passed': False, 'message': f'File not found: {module_path}'}], + 'score': 0, + 'standard': 'STDERR_ROUTING' + } + + # CLI branch is exempt — it defines these functions + if '/cli/apps/' in module_path: + return { + 'passed': True, + 'checks': [{'name': 'Stderr routing', 'passed': True, 'message': 'CLI branch exempt (defines display functions)'}], + 'score': 100, + 'standard': 'STDERR_ROUTING' + } + + try: + with open(path, 'r', encoding='utf-8') as f: + content = f.read() + lines = content.split('\n') + except Exception as e: + return { + 'passed': False, + 'checks': [{'name': 'File readable', 'passed': False, 'message': f'Error reading file: {e}'}], + 'score': 0, + 'standard': 'STDERR_ROUTING' + } + + filename = path.name + + # Check 1: console.print() with error-like red markup + error_prints = _find_error_prints(lines, module_path, bypass_rules) + if error_prints: + checks.append({ + 'name': 'Error output routing', + 'passed': False, + 'message': f'{filename}: {len(error_prints)} error print(s) on lines {error_prints[:5]} — use error() or fatal() instead' + }) + elif _has_any_output(content): + checks.append({ + 'name': 'Error output routing', + 'passed': True, + 'message': 'No error-like console.print() with red markup' + }) + + # Check 2: console.print() with warning-like yellow markup + warning_prints = _find_warning_prints(lines, module_path, bypass_rules) + if warning_prints: + checks.append({ + 'name': 'Warning output routing', + 'passed': False, + 'message': f'{filename}: {len(warning_prints)} warning print(s) on lines {warning_prints[:5]} — use warning() instead' + }) + elif _has_any_output(content): + checks.append({ + 'name': 'Warning output routing', + 'passed': True, + 'message': 'No warning-like console.print() with yellow markup' + }) + + # Check 3: Custom Console(stderr=True) — should import err_console + custom_stderr = _find_custom_stderr_console(lines, module_path, bypass_rules) + if custom_stderr: + checks.append({ + 'name': 'Stderr console creation', + 'passed': False, + 'message': f'{filename}: Custom Console(stderr=True) on lines {custom_stderr[:3]} — import err_console from aipass.cli.apps.modules' + }) + + # No checks applied = no output patterns = skip + if not checks: + return { + 'passed': True, + 'checks': [{'name': 'Stderr routing', 'passed': True, 'message': 'No error/warning output patterns (skipped)'}], + 'score': 100, + 'standard': 'STDERR_ROUTING' + } + + passed_checks = sum(1 for c in checks if c['passed']) + total_checks = len(checks) + score = int((passed_checks / total_checks * 100)) if total_checks > 0 else 0 + + return { + 'passed': score >= 75, + 'checks': checks, + 'score': score, + 'standard': 'STDERR_ROUTING' + } + + +def _has_any_output(content: str) -> bool: + """Check if file has any console output at all.""" + return 'console.print(' in content or 'err_console.print(' in content + + +def _in_skip_context(line: str, in_docstring: bool, in_main_block: bool) -> bool: + """Check if we should skip this line.""" + stripped = line.strip() + if in_docstring or in_main_block: + return True + if stripped.startswith('#'): + return True + return False + + +def _find_error_prints(lines: List[str], module_path: str, bypass_rules: list | None) -> List[int]: + """Find console.print() calls with red markup containing error-like content.""" + violations = [] + in_docstring = False + in_main_block = False + main_block_indent = 0 + + # Patterns: [red], [bold red], [red bold] in console.print() + red_pattern = re.compile(r'console\.print\(.*\[(?:bold\s+)?red(?:\s+bold)?\]', re.IGNORECASE) + # Also catch: console.print(f"❌ ..." or console.print("Error: ...") + error_msg_pattern = re.compile(r'console\.print\([f"\']*[^)]*(?:error|failed|fatal|cannot|unable|invalid|❌)', re.IGNORECASE) + + for i, line in enumerate(lines, 1): + stripped = line.strip() + current_indent = len(line) - len(line.lstrip()) if stripped else 0 + + # Track __main__ blocks + if "if __name__ ==" in stripped and "__main__" in stripped: + in_main_block = True + main_block_indent = current_indent + continue + if in_main_block and stripped and current_indent <= main_block_indent: + in_main_block = False + + # Track docstrings + for quote in ('"""', "'''"): + if line.count(quote) % 2 == 1: + in_docstring = not in_docstring + + if _in_skip_context(line, in_docstring, in_main_block): + continue + + # Skip if in a string context (the console.print itself is in a string) + if 'console.print(' not in stripped: + continue + + # Check it's not inside a string literal + before = line.split('console.print(')[0] + if before.count("'") % 2 == 1 or before.count('"') % 2 == 1: + continue + + # Skip bypassed lines + if is_bypassed(module_path, 'stderr_routing', line=i, bypass_rules=bypass_rules): + continue + + # Match red markup patterns + if red_pattern.search(stripped): + violations.append(i) + elif error_msg_pattern.search(stripped): + # Only flag if it looks like an error message, not just mentioning the word + # Must be the console.print argument, not a variable name + violations.append(i) + + return violations + + +def _find_warning_prints(lines: List[str], module_path: str, bypass_rules: list | None) -> List[int]: + """Find console.print() calls with yellow markup containing warning-like content.""" + violations = [] + in_docstring = False + in_main_block = False + main_block_indent = 0 + + # Patterns: [yellow] in console.print() that look like warnings + yellow_pattern = re.compile(r'console\.print\(.*\[(?:bold\s+)?yellow(?:\s+bold)?\]', re.IGNORECASE) + warning_msg_pattern = re.compile(r'console\.print\([f"\']*[^)]*(?:warning|⚠)', re.IGNORECASE) + + for i, line in enumerate(lines, 1): + stripped = line.strip() + current_indent = len(line) - len(line.lstrip()) if stripped else 0 + + if "if __name__ ==" in stripped and "__main__" in stripped: + in_main_block = True + main_block_indent = current_indent + continue + if in_main_block and stripped and current_indent <= main_block_indent: + in_main_block = False + + for quote in ('"""', "'''"): + if line.count(quote) % 2 == 1: + in_docstring = not in_docstring + + if _in_skip_context(line, in_docstring, in_main_block): + continue + + if 'console.print(' not in stripped: + continue + + before = line.split('console.print(')[0] + if before.count("'") % 2 == 1 or before.count('"') % 2 == 1: + continue + + if is_bypassed(module_path, 'stderr_routing', line=i, bypass_rules=bypass_rules): + continue + + if yellow_pattern.search(stripped): + violations.append(i) + elif warning_msg_pattern.search(stripped): + violations.append(i) + + return violations + + +def _find_custom_stderr_console(lines: List[str], module_path: str, bypass_rules: list | None) -> List[int]: + """Find custom Console(stderr=True) creation — should use err_console import.""" + violations = [] + in_docstring = False + + stderr_pattern = re.compile(r'Console\s*\(.*stderr\s*=\s*True', re.IGNORECASE) + + for i, line in enumerate(lines, 1): + stripped = line.strip() + + for quote in ('"""', "'''"): + if line.count(quote) % 2 == 1: + in_docstring = not in_docstring + + if in_docstring or stripped.startswith('#'): + continue + + if is_bypassed(module_path, 'stderr_routing', line=i, bypass_rules=bypass_rules): + continue + + if stderr_pattern.search(stripped): + violations.append(i) + + return violations diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_content.py b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_content.py new file mode 100644 index 00000000..f9e55df9 --- /dev/null +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_content.py @@ -0,0 +1,73 @@ +# =================== AIPass ==================== +# Name: stderr_routing_content.py +# Description: Stderr Routing Standards Content Handler +# Version: 1.0.0 +# Created: 2026-03-13 +# Modified: 2026-03-13 +# ============================================= + +""" +Stderr Routing Standards Content Handler + +Provides formatted stderr routing standards content. +Module orchestrates, handler implements. +""" + + +def get_stderr_routing_standards() -> str: + """Return formatted stderr routing standards content with Rich markup.""" + lines = [ + "[bold red]STDERR ROUTING STANDARD[/bold red]", + "", + "[yellow]POLICY:[/yellow] Error and warning output MUST route to stderr", + "via CLI display functions. Normal output stays on stdout.", + "", + "[bold cyan]WHY:[/bold cyan]", + " CLI display.py has [dim]err_console = Console(stderr=True)[/dim].", + " [dim]error()[/dim], [dim]warning()[/dim], and [dim]fatal()[/dim] route through it.", + " Branches using [dim]console.print('[red]Error...[/red]')[/dim] bypass this", + " and send errors to stdout, breaking piping and redirection.", + "", + "\u2500" * 70, + "", + "[bold cyan]CORRECT PATTERN:[/bold cyan]", + " [dim]from aipass.cli.apps.modules import error, warning, fatal[/dim]", + "", + " [green]\u2713[/green] [dim]error('Branch not found', suggestion='Check spelling')[/dim]", + " [green]\u2713[/green] [dim]warning('Template mismatch', details='Expected v2')[/dim]", + " [green]\u2713[/green] [dim]fatal('Config missing') # stderr + sys.exit(1)[/dim]", + "", + "[bold cyan]WRONG PATTERN:[/bold cyan]", + " [red]\u2717[/red] [dim]console.print('[red]Error: Branch not found[/red]')[/dim]", + " [red]\u2717[/red] [dim]console.print('[yellow]Warning: mismatch[/yellow]')[/dim]", + " [red]\u2717[/red] [dim]Console(stderr=True) # Don't create your own[/dim]", + "", + "\u2500" * 70, + "", + "[bold cyan]WHAT THE CHECKER CATCHES:[/bold cyan]", + " 1. [dim]console.print()[/dim] with [red][red]/[bold red][/red] markup", + " \u2192 Should use [dim]error()[/dim] or [dim]fatal()[/dim]", + " 2. [dim]console.print()[/dim] with [yellow][yellow]/[bold yellow][/yellow] markup", + " \u2192 Should use [dim]warning()[/dim]", + " 3. Custom [dim]Console(stderr=True)[/dim] creation", + " \u2192 Import [dim]err_console[/dim] from CLI instead", + "", + "[bold cyan]CLI DISPLAY EXPORTS:[/bold cyan]", + " [dim]from aipass.cli.apps.modules import ([/dim]", + " [dim] error, # \u2718 message + optional suggestion \u2192 stderr[/dim]", + " [dim] warning, # \u26a0 message + optional details \u2192 stderr[/dim]", + " [dim] fatal, # \u2718 message + sys.exit(1) \u2192 stderr[/dim]", + " [dim] err_console, # Raw stderr Console (rare, prefer functions)[/dim]", + " [dim])[/dim]", + "", + "[bold cyan]SCOPE:[/bold cyan]", + " All branches except CLI (which defines these functions).", + " Handlers are exempt from CLI output standards (separate concern).", + "", + "[bold cyan]REFERENCE:[/bold cyan]", + " [dim]See: src/aipass/cli/apps/modules/display.py (error, warning, fatal)[/dim]", + " [dim]See: FPLAN-0032 (Phase 1+2: CLI stderr standardization)[/dim]", + " [dim]See: FPLAN-0033 (Phase 3: branch migration — gates on this standard)[/dim]", + ] + + return "\n".join(lines) diff --git a/src/aipass/seedgo/apps/handlers/audit/audit_display.py b/src/aipass/seedgo/apps/handlers/audit/audit_display.py index 62a18bc8..792a02f2 100644 --- a/src/aipass/seedgo/apps/handlers/audit/audit_display.py +++ b/src/aipass/seedgo/apps/handlers/audit/audit_display.py @@ -233,6 +233,16 @@ def print_branch_summary(audit_result: Dict, system_averages: Dict[str, int] | N for issue in violation.get('issues', []): console.print(f" [dim]• {issue}[/dim]") + # Show STDERR_ROUTING violations (console.print red/yellow → use error()/warning()) + stderr_routing_violations = audit_result.get('stderr_routing_violations', []) + if stderr_routing_violations: + console.print() + console.print(f" [bold red]STDERR_ROUTING VIOLATIONS ({len(stderr_routing_violations)} files):[/bold red]") + for violation in stderr_routing_violations: + console.print(f" [red]✗[/red] {violation['path']} [dim](score: {violation['score']}%)[/dim]") + for issue in violation.get('issues', []): + console.print(f" [dim]• {issue}[/dim]") + # Show SHEBANG violations (no #!/... in pip packages) shebang_violations = audit_result.get('shebang_violations', []) if shebang_violations: diff --git a/src/aipass/seedgo/apps/handlers/audit/branch_audit.py b/src/aipass/seedgo/apps/handlers/audit/branch_audit.py index 27507833..7d37dcd1 100644 --- a/src/aipass/seedgo/apps/handlers/audit/branch_audit.py +++ b/src/aipass/seedgo/apps/handlers/audit/branch_audit.py @@ -143,7 +143,14 @@ def audit_branch(branch: Dict[str, str], bypass_rules: list, pack_path: Path = N v, s = _run_all_files(checker, name, all_files, bypass_rules) all_violations[name] = v if s: - scores[name] = int(sum(s) / len(s)) + avg_score = int(sum(s) / len(s)) + scores[name] = avg_score + # Update results to reflect all-files findings + all_failed = [] + for vi in v: + all_failed.extend({"name": name, "passed": False, "message": iss} for iss in vi.get("issues", [])) + if all_failed: + results[name] = {"passed": avg_score >= 75, "checks": all_failed, "score": avg_score, "standard": name.upper()} # Log structure post-checks (audit-level, not in any checker) if "log_structure" in scores: diff --git a/src/aipass/seedgo/apps/modules/standards_audit.py b/src/aipass/seedgo/apps/modules/standards_audit.py index a8c9e2a7..5a6cc6b7 100755 --- a/src/aipass/seedgo/apps/modules/standards_audit.py +++ b/src/aipass/seedgo/apps/modules/standards_audit.py @@ -32,6 +32,7 @@ from aipass.prax import logger # CLI services (display/output formatting) from aipass.cli import console, header +from aipass.cli.apps.modules import error, warning # JSON handler for tracking from aipass.seedgo.apps.handlers.json import json_handler @@ -204,21 +205,18 @@ def handle_command(command: str, args: List[str]) -> bool: # Validate pack name packs = _discover_packs() if pack_name not in packs: - console.print(f"\n[red]Unknown pack:[/red] '{pack_name}'") - console.print() - console.print("[yellow]Available packs:[/yellow]") - for name in packs: - console.print(f" [cyan]{name}[/cyan]") - console.print() - console.print(f"[dim]Usage: drone @seedgo audit {next(iter(packs), '')}[/dim]") - console.print() + available = ", ".join(packs.keys()) + error( + f"Unknown pack: '{pack_name}'", + suggestion=f"Available packs: {available}. Usage: drone @seedgo audit {next(iter(packs), '')}" + ) return True pack_path = packs[pack_name] # Handle --show-bypasses mode (placeholder — bypass audit merged into audit per D11) if show_bypasses: - console.print("[yellow]--show-bypasses not yet implemented in seedgo[/yellow]") + warning("--show-bypasses not yet implemented in seedgo") return True # ========================================================================= diff --git a/src/aipass/seedgo/apps/seedgo.py b/src/aipass/seedgo/apps/seedgo.py index b0ba4f34..46c6207c 100644 --- a/src/aipass/seedgo/apps/seedgo.py +++ b/src/aipass/seedgo/apps/seedgo.py @@ -22,6 +22,7 @@ from typing import List, Any from aipass.prax import logger from aipass.cli import console, header +from aipass.cli.apps.modules import error VERSION = "2.0.0" SEEDGO_ROOT = Path(__file__).parent @@ -228,8 +229,7 @@ def main() -> int: if route_command(command, remaining, modules): return 0 - console.print(f"[red]Unknown command:[/red] {command}") - console.print("Run 'seedgo --help' for usage") + error(f"Unknown command: {command}", suggestion="Run 'seedgo --help' for usage") return 1 diff --git a/src/aipass/spawn/apps/modules/delete.py b/src/aipass/spawn/apps/modules/delete.py index ff683af4..945bdcbe 100644 --- a/src/aipass/spawn/apps/modules/delete.py +++ b/src/aipass/spawn/apps/modules/delete.py @@ -14,7 +14,7 @@ All implementation logic lives in apps/handlers/delete_ops.py. from aipass.prax import logger # CLI service: from cli.apps.modules import console (via aipass namespace) -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.spawn.apps.handlers.delete_ops import delete_branch @@ -65,7 +65,7 @@ def handle_delete(args: list[str]) -> int: Returns exit code (0=success, 1=failure). """ if not args: - console.print("[yellow]Usage: drone @spawn delete <@branch> [--yes] [--dry-run][/yellow]") + warning("Usage: drone @spawn delete <@branch> [--yes] [--dry-run]") console.print() console.print(" [green]@branch[/green] Branch to archive and deregister") console.print(" [green]--yes[/green] Skip confirmation prompt") @@ -79,7 +79,7 @@ def handle_delete(args: list[str]) -> int: targets = [a for a in args if not a.startswith("--")] if not targets: - console.print("[red]Error: specify a branch name (e.g. @api)[/red]") + error("specify a branch name (e.g. @api)") return 1 branch_name = targets[0].lstrip("@").lower() @@ -92,7 +92,7 @@ def handle_delete(args: list[str]) -> int: ) except Exception as exc: logger.error(f"[delete] Unexpected error deleting {branch_name}: {exc}") - console.print(f"[red]Error deleting {branch_name}: {exc}[/red]") + error(f"Error deleting {branch_name}: {exc}") return 1 _print_summary(result, dry_run) @@ -113,7 +113,7 @@ def _print_summary(result: dict, dry_run: bool) -> None: if success: console.print(f"[green]Delete {mode}{branch}[/green]") else: - console.print(f"[red]Delete FAILED {mode}{branch}[/red]") + error(f"Delete FAILED {mode}{branch}") archive_path = result.get("archive_path", "") if archive_path: @@ -121,8 +121,8 @@ def _print_summary(result: dict, dry_run: bool) -> None: console.print(f" Registry updated: {result.get('registry_updated', False)}") - error = result.get("error", "") - if error: - console.print(f" [red]Error: {error}[/red]") + err_msg = result.get("error", "") + if err_msg: + error(err_msg) console.print() diff --git a/src/aipass/spawn/apps/modules/passport.py b/src/aipass/spawn/apps/modules/passport.py index 8632c30e..4635f301 100644 --- a/src/aipass/spawn/apps/modules/passport.py +++ b/src/aipass/spawn/apps/modules/passport.py @@ -15,7 +15,7 @@ All implementation logic lives in apps/handlers/passport_ops.py. import argparse from aipass.prax import logger -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.spawn.apps.handlers.passport_ops import grant_passport @@ -58,13 +58,13 @@ def handle_passport(args: list[str]) -> int: Returns exit code (0=success, 1=failure). """ if not args: - console.print("[yellow]Usage: drone @spawn passport <@dirname> [--role ...] [--purpose ...][/yellow]") + warning("Usage: drone @spawn passport <@dirname> [--role ...] [--purpose ...]") console.print() console.print(" [dim]Grant birthright citizenship to a directory[/dim]") console.print() console.print(" [green]@dirname[/green] Directory to grant citizenship (created if needed)") - console.print(" [yellow]--role[/yellow] Role description for the passport") - console.print(" [yellow]--purpose[/yellow] Purpose description") + warning("--role", details="Role description for the passport") + warning("--purpose", details="Purpose description") return 1 parser = argparse.ArgumentParser(prog="spawn passport", add_help=False) @@ -96,9 +96,9 @@ def handle_passport(args: list[str]) -> int: console.print(f" Files: {result['files_copied']}") console.print(f" Registry: {'updated' if result['registry_updated'] else 'not updated'}") if result["validation_issues"]: - console.print(f" [yellow]Warnings: {len(result['validation_issues'])} unreplaced placeholders[/yellow]") + warning(f"{len(result['validation_issues'])} unreplaced placeholders") console.print() return 0 else: - console.print(f"[red]Error: {result['error']}[/red]") + error(result['error']) return 1 diff --git a/src/aipass/spawn/apps/modules/sync_registry.py b/src/aipass/spawn/apps/modules/sync_registry.py index 8afe4342..6e2c200c 100644 --- a/src/aipass/spawn/apps/modules/sync_registry.py +++ b/src/aipass/spawn/apps/modules/sync_registry.py @@ -14,7 +14,7 @@ All implementation logic lives in apps/handlers/sync_registry_ops.py. from aipass.prax import logger # CLI service: from cli.apps.modules import console (via aipass namespace) -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.spawn.apps.handlers.sync_registry_ops import sync_registry @@ -64,7 +64,7 @@ def handle_sync_registry(args: list[str]) -> int: Returns exit code (0=success, 1=failure). """ if args and args[0] in ["--help", "-h"]: - console.print("[yellow]Usage: drone @spawn sync-registry [--fix][/yellow]") + warning("Usage: drone @spawn sync-registry [--fix]") console.print() console.print(" [green](no args)[/green] Report mismatches between registry and filesystem") console.print(" [green]--fix[/green] Auto-repair: remove stale, add unregistered") @@ -76,7 +76,7 @@ def handle_sync_registry(args: list[str]) -> int: result = sync_registry(fix=fix) except Exception as exc: logger.error(f"[sync-registry] Unexpected error: {exc}") - console.print(f"[red]Error: {exc}[/red]") + error(str(exc)) return 1 _print_summary(result) @@ -106,7 +106,7 @@ def _print_summary(result: dict) -> None: # Stale if stale: - console.print(f" [red]Stale ({len(stale)}):[/red] [dim]registered but missing/invalid[/dim]") + error(f"Stale ({len(stale)}): registered but missing/invalid") for name in sorted(stale): console.print(f" {name}") else: @@ -114,7 +114,7 @@ def _print_summary(result: dict) -> None: # Unregistered if unregistered: - console.print(f" [yellow]Unregistered ({len(unregistered)}):[/yellow] [dim]on disk but not in registry[/dim]") + warning(f"Unregistered ({len(unregistered)}): on disk but not in registry") for name in sorted(unregistered): console.print(f" {name}") else: diff --git a/src/aipass/spawn/apps/modules/sync_templates.py b/src/aipass/spawn/apps/modules/sync_templates.py index 5175b99e..bd00d81c 100644 --- a/src/aipass/spawn/apps/modules/sync_templates.py +++ b/src/aipass/spawn/apps/modules/sync_templates.py @@ -14,7 +14,7 @@ All implementation logic lives in apps/handlers/sync_templates_ops.py. from aipass.prax import logger # CLI service: from cli.apps.modules import console (via aipass namespace) -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.spawn.apps.handlers.sync_templates_ops import sync_templates @@ -66,7 +66,7 @@ def handle_sync_templates(args: list[str]) -> int: Returns exit code (0=success, 1=failure). """ if args and args[0] in ["--help", "-h"]: - console.print("[yellow]Usage: drone @spawn sync-templates [--status|--sync|--dry-run][/yellow]") + warning("Usage: drone @spawn sync-templates [--status|--sync|--dry-run]") console.print() console.print(" [green](no args)[/green] Report which managed template files are stale") console.print(" [green]--status[/green] Same as no args") @@ -81,7 +81,7 @@ def handle_sync_templates(args: list[str]) -> int: result = sync_templates(sync=sync, dry_run=dry_run) except Exception as exc: logger.error(f"[sync-templates] Unexpected error: {exc}") - console.print(f"[red]Error: {exc}[/red]") + error(str(exc)) return 1 _print_summary(result, dry_run) @@ -119,7 +119,7 @@ def _print_summary(result: dict, dry_run: bool) -> None: console.print(f" {name}") if stale: - console.print(f" [yellow]Stale ({len(stale)}):[/yellow]") + warning(f"Stale ({len(stale)}):") for name in stale: console.print(f" {name}") @@ -129,7 +129,7 @@ def _print_summary(result: dict, dry_run: bool) -> None: console.print(f" {name}") if errors: - console.print(f" [red]Errors ({len(errors)}):[/red]") + error(f"Errors ({len(errors)}):") for err in errors: console.print(f" {err}") diff --git a/src/aipass/spawn/apps/modules/update.py b/src/aipass/spawn/apps/modules/update.py index 966870dc..8e174843 100644 --- a/src/aipass/spawn/apps/modules/update.py +++ b/src/aipass/spawn/apps/modules/update.py @@ -14,7 +14,7 @@ All implementation logic lives in apps/handlers/update_ops.py. from aipass.prax import logger # CLI service: from cli.apps.modules import console (via aipass namespace) -from aipass.cli.apps.modules import console +from aipass.cli.apps.modules import console, error, warning from aipass.spawn.apps.handlers.update_ops import ( update_branch, @@ -72,7 +72,7 @@ def handle_update(args: list[str]) -> int: Returns exit code (0=success, 1=failure). """ if not args: - console.print("[yellow]Usage: drone @spawn update <@branch|class --all> [--dry-run] [--trace][/yellow]") + warning("Usage: drone @spawn update <@branch|class --all> [--dry-run] [--trace]") console.print() console.print(" [green]@branch[/green] Update a single branch (uses its own class)") console.print(" [green]builder --all[/green] Update all builder-class branches") @@ -100,9 +100,7 @@ def handle_update(args: list[str]) -> int: if "--all" in args: if citizen_class is None: classes = ", ".join(get_available_classes()) - console.print(f"[red]Error: --all requires a citizen class[/red]") - console.print(f"[dim]Specify a class: drone @spawn update --all[/dim]") - console.print(f"[dim]Available classes: {classes}[/dim]") + error("--all requires a citizen class", suggestion=f"drone @spawn update --all (available: {classes})") return 1 results = update_all(dry_run=dry_run, trace=trace, citizen_class=citizen_class) @@ -110,7 +108,7 @@ def handle_update(args: list[str]) -> int: return 0 if all(r.get("success") for r in results) else 1 if not targets: - console.print("[red]Error: specify a branch name (e.g. @prax) or use --all[/red]") + error("specify a branch name (e.g. @prax) or use --all") return 1 # Take the first target, strip leading @ @@ -120,7 +118,7 @@ def handle_update(args: list[str]) -> int: result = update_branch(branch_name, dry_run=dry_run, trace=trace) except Exception as exc: logger.error(f"[update] Unexpected error updating {branch_name}: {exc}") - console.print(f"[red]Error updating {branch_name}: {exc}[/red]") + error(f"Error updating {branch_name}: {exc}") return 1 _print_branch_summary(result, dry_run) @@ -141,7 +139,7 @@ def _print_branch_summary(result: dict, dry_run: bool) -> None: if success: console.print(f"[green]Update {mode}{branch}[/green]") else: - console.print(f"[red]Update FAILED {mode}{branch}[/red]") + error(f"Update FAILED {mode}{branch}") console.print(f" Additions: {result.get('additions', 0)}") console.print(f" Renames: {result.get('renames', 0)}") @@ -151,7 +149,7 @@ def _print_branch_summary(result: dict, dry_run: bool) -> None: errs = result.get("errors", []) if errs: - console.print(f" [red]Errors: {len(errs)}[/red]") + error(f"Errors: {len(errs)}") for e in errs: console.print(f" - {e}") @@ -223,5 +221,5 @@ def _print_all_summary(results: list[dict], dry_run: bool) -> None: f"!{total_skip} py-skipped") if total_err: - console.print(f" [red]{total_err} errors across all branches[/red]") + error(f"{total_err} errors across all branches") console.print() diff --git a/src/aipass/spawn/apps/spawn.py b/src/aipass/spawn/apps/spawn.py index e7cfb905..dec3dd1a 100644 --- a/src/aipass/spawn/apps/spawn.py +++ b/src/aipass/spawn/apps/spawn.py @@ -17,7 +17,7 @@ import sys import argparse from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console, header +from aipass.cli.apps.modules import console, header, error, warning def print_help(): @@ -52,13 +52,13 @@ def print_help(): console.print() console.print("[bold cyan]OPTIONS:[/bold cyan]") console.print() - console.print(" [yellow]--role[/yellow] Agent role description") - console.print(" [yellow]--traits[/yellow] Agent personality traits") - console.print(" [yellow]--purpose[/yellow] Agent purpose (brief)") - console.print(" [yellow]--template[/yellow] Custom template directory") - console.print(" [yellow]--registry[/yellow] Path to AIPASS_REGISTRY.json") - console.print(" [yellow]--dry-run[/yellow] Preview changes without modifying files") - console.print(" [yellow]--trace[/yellow] Enable verbose logging") + warning("--role", details="Agent role description") + warning("--traits", details="Agent personality traits") + warning("--purpose", details="Agent purpose (brief)") + warning("--template", details="Custom template directory") + warning("--registry", details="Path to AIPASS_REGISTRY.json") + warning("--dry-run", details="Preview changes without modifying files") + warning("--trace", details="Enable verbose logging") console.print() @@ -68,8 +68,7 @@ def handle_create(args): from aipass.spawn.apps.modules.core import validate_class, get_default_class if not args: - console.print("[red]Error: target path required[/red]") - console.print("[dim]Usage: drone @spawn create [class] [--role ...][/dim]") + error("target path required", suggestion="drone @spawn create [class] [--role ...]") return 1 # Check if first arg is a citizen class @@ -79,7 +78,7 @@ def handle_create(args): citizen_class = args[0] remaining_args = args[1:] if not remaining_args: - console.print("[red]Error: target path required after class name[/red]") + error("target path required after class name") return 1 parser = argparse.ArgumentParser(prog="spawn create", add_help=False) @@ -110,11 +109,11 @@ def handle_create(args): console.print(f" Files: {result['files_copied']}") console.print(f" Registry: {'updated' if result['registry_updated'] else 'not updated'}") if result["validation_issues"]: - console.print(f" [yellow]Warnings: {len(result['validation_issues'])} unreplaced placeholders[/yellow]") + warning(f"{len(result['validation_issues'])} unreplaced placeholders") console.print() return 0 else: - console.print(f"[red]Error: {result['error']}[/red]") + error(result['error']) return 1 @@ -183,12 +182,10 @@ def main(): } if command in stub_commands: - console.print(f"[yellow]'{command}' is not yet implemented.[/yellow]") - console.print(f"[dim]Planned: {stub_commands[command]}[/dim]") + warning(f"'{command}' is not yet implemented.", details=f"Planned: {stub_commands[command]}") return 1 - console.print(f"[red]Unknown command: {command}[/red]") - console.print("[dim]Run 'drone @spawn --help' for available commands[/dim]") + error(f"Unknown command: {command}", suggestion="Run 'drone @spawn --help' for available commands") return 1 diff --git a/src/aipass/trigger/apps/modules/core.py b/src/aipass/trigger/apps/modules/core.py index 52d289e6..c017b701 100644 --- a/src/aipass/trigger/apps/modules/core.py +++ b/src/aipass/trigger/apps/modules/core.py @@ -185,7 +185,7 @@ def handle_command(command: str, args: list) -> bool: Returns: True if command was handled, False otherwise """ - from aipass.cli.apps.modules import console + from aipass.cli.apps.modules import console, error # Handle module-name routing (drone @trigger core ) if command == "core": @@ -203,7 +203,7 @@ def handle_command(command: str, args: list) -> bool: if command == "fire": if not args: - console.print("[red]Usage: drone @trigger fire [key=value ...][/red]") + error("Usage: drone @trigger fire [key=value ...]") return True event_name = args[0] # Parse key=value pairs from remaining args diff --git a/src/aipass/trigger/apps/modules/errors.py b/src/aipass/trigger/apps/modules/errors.py index ba5bc937..e757acc3 100644 --- a/src/aipass/trigger/apps/modules/errors.py +++ b/src/aipass/trigger/apps/modules/errors.py @@ -147,7 +147,7 @@ def handle_command(command: str, args: list) -> bool: Returns: True if command was handled, False otherwise """ - from aipass.cli.apps.modules import console + from aipass.cli.apps.modules import console, error if command != "errors": return False @@ -170,8 +170,7 @@ def handle_command(command: str, args: list) -> bool: if sub in routes: return routes[sub](console, rest) - console.print(f"[red]Unknown subcommand: {sub}[/red]") - console.print("Run [dim]drone @trigger errors help[/dim] for available commands") + error(f"Unknown subcommand: {sub}", suggestion="Run 'drone @trigger errors help' for available commands") return True @@ -229,16 +228,15 @@ def _cmd_list(console, args: list) -> bool: def _cmd_detail(console, args: list) -> bool: """Show full error details for a fingerprint or ID.""" from rich.panel import Panel + from aipass.cli.apps.modules import error if not args: - console.print("[red]Missing error ID or fingerprint[/red]") - console.print("Usage: drone @trigger errors detail ") + error("Missing error ID or fingerprint", suggestion="Usage: drone @trigger errors detail ") return True entry = _find_by_id_or_fp(args[0]) if not entry: - console.print(f"[red]Error not found:[/red] {args[0]}") - console.print(" [dim]Try a fingerprint prefix, full fingerprint, or short ID[/dim]") + error(f"Error not found: {args[0]}", suggestion="Try a fingerprint prefix, full fingerprint, or short ID") return True st = entry.get("status", "?") diff --git a/src/aipass/trigger/apps/trigger.py b/src/aipass/trigger/apps/trigger.py index a2fd603d..73e17509 100644 --- a/src/aipass/trigger/apps/trigger.py +++ b/src/aipass/trigger/apps/trigger.py @@ -27,7 +27,7 @@ from typing import List, Any from aipass.prax.apps.modules.logger import system_logger as logger # CLI services for formatted output -from aipass.cli.apps.modules import console, header +from aipass.cli.apps.modules import console, header, error # ============================================================================= # MODULE DISCOVERY @@ -209,9 +209,7 @@ def main(): return 0 else: console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]python3 trigger.py --help[/dim] for available commands") + error(f"Unknown command: {command}", suggestion="Run 'python3 trigger.py --help' for available commands") console.print() return 1