From 557ab92c31e8d1420b309e58c604197cb67fa778 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Mon, 23 Mar 2026 16:17:57 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20system-wide=20silent=20catch=20sprint?= =?UTF-8?q?=20=E2=80=94=2013=20branches,=2093%=20avg=20compliance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dispatched all 14 branches (excl seedgo) for silent catch fixes. ~600 violations fixed across 150+ files. Bypass entries added for spawn (7 deep nesting) and commons (9 deep nesting). DPLAN-0052 tracks the sprint. README updated with current system state. Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 20 +++---- src/aipass/cli/.seedgo/bypass.json | 10 ++++ src/aipass/cli/apps/cli.py | 1 + .../cli/apps/handlers/json/json_handler.py | 1 - src/aipass/cli/apps/modules/display.py | 18 +++--- src/aipass/cli/apps/modules/init_project.py | 7 ++- src/aipass/cli/apps/modules/templates.py | 11 ++-- src/aipass/daemon/apps/daemon.py | 1 + src/aipass/daemon/apps/daemon_wakeup.py | 7 ++- .../apps/handlers/actions/actions_registry.py | 36 +++++++----- .../daemon/apps/handlers/json/json_handler.py | 10 ++-- .../handlers/monitoring/activity_collector.py | 16 +++--- .../apps/handlers/monitoring/memory_health.py | 4 ++ .../handlers/monitoring/red_flag_detector.py | 5 +- .../handlers/monitoring/report_generator.py | 3 +- .../apps/handlers/schedule/task_registry.py | 20 +++++-- .../apps/handlers/update/data_loader.py | 7 ++- src/aipass/daemon/apps/modules/actions.py | 4 ++ .../daemon/apps/modules/activity_report.py | 4 +- src/aipass/daemon/apps/modules/schedule.py | 6 +- .../daemon/apps/modules/scheduler_ops.py | 5 ++ src/aipass/daemon/apps/modules/wakeup_ops.py | 3 + src/aipass/daemon/apps/plugins/__init__.py | 5 +- .../daemon/apps/plugins/community_rotation.py | 5 +- src/aipass/daemon/apps/scheduler_cron.py | 42 +++++++++++--- src/aipass/drone/apps/drone.py | 7 +++ .../drone/apps/handlers/discovery_handler.py | 4 +- .../drone/apps/handlers/git/lock_handler.py | 5 ++ .../drone/apps/handlers/git/pr_handler.py | 1 + .../drone/apps/handlers/git/status_handler.py | 1 + .../drone/apps/handlers/json/json_handler.py | 6 +- .../apps/handlers/module_registry_handler.py | 9 ++- .../drone/apps/handlers/registry_handler.py | 6 +- .../drone/apps/handlers/router_handler.py | 1 + .../apps/handlers/scanning/formatters.py | 2 + .../drone/apps/handlers/scanning/scanner.py | 1 + src/aipass/drone/apps/modules/commands.py | 2 + src/aipass/drone/apps/modules/config.py | 1 + src/aipass/drone/apps/modules/discovery.py | 1 + src/aipass/drone/apps/modules/git_module.py | 3 + .../drone/apps/modules/module_registry.py | 1 + src/aipass/drone/apps/modules/registry.py | 1 + src/aipass/drone/apps/modules/resolver.py | 1 + src/aipass/drone/apps/modules/router.py | 1 + src/aipass/drone/apps/modules/scan.py | 3 + src/aipass/drone/drone_adapter.py | 3 + .../flow/.aipass/aipass_local_prompt.md | 2 +- src/aipass/flow/README.md | 2 +- src/aipass/flow/apps/flow.py | 3 +- .../flow/apps/handlers/config/load_config.py | 7 ++- .../dashboard/push_branch_dashboard.py | 20 ++++--- .../apps/handlers/dashboard/push_central.py | 10 +++- .../apps/handlers/dashboard/update_local.py | 10 +++- .../flow/apps/handlers/json/json_handler.py | 21 ++++--- .../flow/apps/handlers/mbank/process.py | 28 ++++++---- .../flow/apps/handlers/plan/aggregate_ops.py | 4 +- .../handlers/plan/calculate_relative_path.py | 2 + .../flow/apps/handlers/plan/close_ops.py | 14 +++-- .../flow/apps/handlers/plan/command_parser.py | 6 +- .../flow/apps/handlers/plan/confirmation.py | 2 + src/aipass/flow/apps/handlers/plan/create.py | 5 +- .../flow/apps/handlers/plan/create_file.py | 1 + .../flow/apps/handlers/plan/create_ops.py | 2 +- src/aipass/flow/apps/handlers/plan/display.py | 5 +- .../flow/apps/handlers/plan/get_open_plans.py | 11 +++- .../flow/apps/handlers/plan/list_ops.py | 7 ++- .../flow/apps/handlers/plan/restore_ops.py | 5 +- .../apps/handlers/registry/load_registry.py | 4 +- .../apps/handlers/registry/save_registry.py | 4 +- .../handlers/summary/write_plan_outputs.py | 22 +++++--- .../apps/handlers/template/list_templates.py | 4 +- src/aipass/flow/apps/modules/list_plans.py | 7 ++- .../flow/templates/flow_plans/default.md | 10 ++-- .../flow/templates/flow_plans/master.md | 10 ++-- .../prax/apps/handlers/central/reader.py | 6 +- .../apps/handlers/config/ignore_patterns.py | 8 ++- src/aipass/prax/apps/handlers/config/load.py | 5 +- .../handlers/dashboard/agent_status_writer.py | 21 +++++-- .../apps/handlers/dashboard/operations.py | 16 ++++-- .../prax/apps/handlers/dashboard/refresh.py | 26 +++++---- .../handlers/dashboard/template_differ.py | 8 ++- .../handlers/dashboard/template_pusher.py | 17 +++++- .../prax/apps/handlers/discovery/scanner.py | 14 +++-- .../prax/apps/handlers/discovery/watcher.py | 13 +++-- .../prax/apps/handlers/json/json_handler.py | 22 +++++--- .../apps/handlers/logging/introspection.py | 6 +- .../apps/handlers/logging/log_watchdog.py | 11 +++- .../prax/apps/handlers/logging/operations.py | 9 +-- .../prax/apps/handlers/logging/setup.py | 8 ++- .../handlers/logging/terminal/filtering.py | 6 +- .../apps/handlers/monitoring/event_queue.py | 8 ++- .../handlers/monitoring/filesystem_handler.py | 6 +- .../apps/handlers/monitoring/log_watcher.py | 12 ++-- .../handlers/monitoring/unified_stream.py | 6 +- .../prax/apps/handlers/registry/load.py | 7 ++- .../prax/apps/handlers/registry/save.py | 7 ++- .../prax/apps/handlers/watcher/monitor.py | 6 +- src/aipass/prax/apps/modules/dashboard.py | 2 + src/aipass/prax/apps/modules/logger.py | 17 ++++-- src/aipass/prax/apps/modules/monitor.py | 6 +- src/aipass/prax/apps/prax.py | 2 + src/aipass/spawn/.seedgo/bypass.json | 30 ++++++++++ src/aipass/spawn/apps/handlers/delete_ops.py | 3 +- src/aipass/spawn/apps/handlers/file_ops.py | 10 ++-- .../spawn/apps/handlers/json/json_handler.py | 22 +++++--- src/aipass/spawn/apps/handlers/meta_ops.py | 4 +- .../spawn/apps/handlers/placeholders.py | 7 ++- src/aipass/spawn/apps/handlers/reconcile.py | 3 +- src/aipass/spawn/apps/handlers/registry.py | 7 ++- .../spawn/apps/handlers/sync_registry_ops.py | 4 +- .../spawn/apps/handlers/sync_templates_ops.py | 3 +- src/aipass/spawn/apps/handlers/update_ops.py | 6 +- src/aipass/spawn/apps/modules/core.py | 3 +- src/aipass/spawn/apps/modules/passport.py | 1 + src/aipass/spawn/apps/spawn.py | 1 + .../trigger/apps/handlers/error_registry.py | 25 ++++++--- .../trigger/apps/handlers/error_reporter.py | 4 +- .../apps/handlers/events/bulletin_created.py | 40 ++++++++++---- .../apps/handlers/events/error_detected.py | 55 +++++++++++++------ .../apps/handlers/events/error_logged.py | 31 +++++++++-- .../events/memory_threshold_exceeded.py | 20 ++++++- .../trigger/apps/handlers/events/startup.py | 46 ++++++++++++---- .../apps/handlers/json/json_handler.py | 17 +++++- .../trigger/apps/handlers/log_watcher.py | 30 ++++++---- .../trigger/apps/handlers/medic_state.py | 15 +++-- .../apps/handlers/watchers/log_watcher.py | 15 ++--- src/commons/.seedgo/bypass.json | 11 +++- src/commons/apps/commons.py | 1 + .../apps/handlers/activity/activity_ops.py | 4 +- .../apps/handlers/artifacts/artifact_ops.py | 5 ++ .../apps/handlers/artifacts/capsule_ops.py | 2 + .../apps/handlers/artifacts/trade_ops.py | 5 ++ .../apps/handlers/catchup/catchup_ops.py | 5 +- .../apps/handlers/central/central_writer.py | 1 + .../apps/handlers/comments/comment_ops.py | 3 + .../apps/handlers/curation/curation_ops.py | 5 ++ .../handlers/dashboard/dashboard_writer.py | 3 + src/commons/apps/handlers/database/db.py | 2 + .../apps/handlers/digest/digest_ops.py | 2 +- .../handlers/engagement/engagement_ops.py | 4 +- src/commons/apps/handlers/feed/feed_ops.py | 7 ++- .../apps/handlers/identity/identity_ops.py | 1 + .../apps/handlers/json/json_handler.py | 1 + .../notifications/dashboard_pipeline.py | 2 +- .../notifications/notification_ops.py | 5 +- .../handlers/notifications/preferences.py | 2 +- src/commons/apps/handlers/posts/post_ops.py | 2 + .../apps/handlers/profiles/profile_ops.py | 6 +- .../apps/handlers/profiles/profile_queries.py | 2 + .../apps/handlers/rooms/explore_ops.py | 4 +- .../apps/handlers/rooms/room_state_ops.py | 8 +++ src/commons/apps/handlers/rooms/space_ops.py | 4 ++ .../apps/handlers/search/search_ops.py | 5 +- .../apps/handlers/social/leaderboard_ops.py | 2 +- .../apps/handlers/welcome/welcome_handler.py | 2 +- .../apps/handlers/welcome/welcome_ops.py | 2 +- src/commons/apps/modules/activity.py | 1 + src/commons/apps/modules/artifact.py | 1 + src/commons/apps/modules/capsule.py | 1 + src/commons/apps/modules/catchup.py | 1 + src/commons/apps/modules/central.py | 1 + src/commons/apps/modules/comment.py | 1 + src/commons/apps/modules/commons_identity.py | 1 + src/commons/apps/modules/database.py | 1 + src/commons/apps/modules/digest.py | 1 + src/commons/apps/modules/engagement.py | 1 + src/commons/apps/modules/explore.py | 1 + src/commons/apps/modules/feed.py | 1 + src/commons/apps/modules/leaderboard.py | 1 + src/commons/apps/modules/notification.py | 1 + src/commons/apps/modules/post.py | 1 + src/commons/apps/modules/profile.py | 1 + src/commons/apps/modules/reaction.py | 1 + src/commons/apps/modules/room.py | 1 + src/commons/apps/modules/search.py | 1 + src/commons/apps/modules/space.py | 3 +- src/commons/apps/modules/trade.py | 1 + src/commons/apps/modules/welcome.py | 1 + src/skills/apps/handlers/discovery_handler.py | 5 +- src/skills/apps/handlers/json/json_handler.py | 7 ++- src/skills/apps/handlers/loader_handler.py | 1 + src/skills/apps/handlers/runner_handler.py | 3 + src/skills/apps/handlers/template.py | 5 +- src/skills/apps/handlers/validator.py | 2 + src/skills/apps/modules/creator.py | 1 + 185 files changed, 990 insertions(+), 382 deletions(-) diff --git a/README.md b/README.md index 5c926963..a99cafa4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ An operating system for AI agents. Not a chatbot wrapper. Not a prompt chain. A - **15 citizens** work in the same filesystem without isolation (no git worktrees, no sandboxes) - **Dispatch locks** prevent conflicts — if an agent is working, incoming tasks queue instead of spawning duplicates - **Persistent memory** survives across sessions via `.trinity/` files (identity, session history, collaboration patterns) -- **Standards enforcement** keeps the system consistent as it grows (seedgo runs 24 automated checks) +- **Standards enforcement** keeps the system consistent as it grows (seedgo runs 34 automated checks, system-wide avg 93% compliance) - **Diagnostic tooling** — 20 standalone scanners cover code quality, security, documentation, and compliance - **Inter-agent messaging** lets citizens email each other, dispatch tasks, and wake each other up - **Everything is tracked** — design plans (DPLANs), execution plans (FPLANs), and seedgo audits make changes traceable even when 500+ files change in a single session @@ -24,24 +24,22 @@ An operating system for AI agents. Not a chatbot wrapper. Not a prompt chain. A ## Current State: Beta -**It works.** All 15 branches operational. 106+ PRs merged. 44 orchestration sessions. 744 tests across the system. 173 drone commands discovered. The system is past prototyping — we're in the hardening phase, with diagnostic tooling complete and seedgo integration next. +**It works.** All 15 branches operational. 111+ PRs merged. 48 orchestration sessions. 744 tests across the system. 173 drone commands discovered. System-wide compliance at 93% average across 34 automated standards checks. **Recently completed:** -- **Diagnostic tooling suite** — 20 standalone scanners built, validated against API ground truth, and personally reviewed. Covers: silent catches, dead code, unused functions, deep nesting, debug prints, commented loggers, hardcoded keys, URL injection, magic numbers, TODO tracking, help text, README freshness, stale terminology, command verification, test coverage, prompt quality, and more. All follow `{concern}_scanner_v1.py` convention with `@branch` / `--all` / `--summary` flags. -- **Seedgo integration research** — 15-agent analysis mapped how all 20 scanners integrate into seedgo's auto-discovery checker system. 10 tools map to hard checks, 7 are advisory (investigation tools). Integration plan ready for dispatch. -- **3-tier logging revision** — handlers can now use `logger.info/warning` (previously prohibited). 112 commented-out logger calls across API handlers alone. DPLAN-0040 created for seedgo standard update. -- **Branch audit cycle** — DPLANs created per branch (API, commons, ai_mail, spawn, backup, prax, memory, drone). 4 branches dispatched and verified (commons DB path fixed, ai_mail purge consolidated, spawn templates updated, backup archived 8 files). +- **System-wide compliance sprint** — all 14 branches audited and dispatched for silent catch fixes in a single session. 13 branches completed autonomously (8 running in parallel). ~600 silent catch violations fixed across 150+ files. System average went from ~88% to 93%. Tracked via DPLAN-0052. +- **Seedgo 34-standard audit pack** — 10 new checkers integrated from diagnostic tools (silent catch, deep nesting, debug print, commented logger, unused function, dead code, help text, hardcoded key, test coverage, TODO). All auto-discovered via `*_check.py` pattern. Bypass system working with `.seedgo/bypass.json` per branch. +- **Deep nesting investigation** — spawn (12 functions: 7 justified, 5 refactorable), commons (14 functions: 9 justified, 5 refactorable), API (13 functions), ai_mail (27 functions), backup (6 functions). Justified functions bypassed, refactorable ones queued. - **Dispatch UX redesign** — `drone @ai_mail dispatch @target "Subject" "Body"` sends + wakes in one command. `--fresh` flag for clean sessions. `email` command for mail-only (no wake). Fully tested. - **PR v2 workflow** — commit-on-main architecture. Changes never leave your working tree. Feature branches are just pointers for GitHub's PR system. No more disappearing files. - **Prax monitor** — fully operational with inotify file watching, branch detection, full message display. Used as secondary terminal to work around Claude Code's scroll limitation. **What we're solving now:** -- **Seedgo checker integration** — porting diagnostic tool logic into seedgo's `*_check.py` auto-discovery pattern. Goal: `drone @seedgo audit aipass` runs 34+ checks (24 existing + 10 new from diagnostic tools). -- **Branch-by-branch audit** — walking through every branch from devpulse, testing commands, noting issues, dispatching fixes. API branch audit complete (DPLAN-0029), 6 more branch DPLANs in progress. -- **Local prompt enrichment** — template finalized (DPLAN-0032), ready for rollout to 5 stub branches. -- **Test coverage expansion** — 9 branches have zero tests. 744 tests total, 27% module coverage. Test scanner provides per-branch visibility. +- **Deep nesting compliance** — 73% average across system. Bypass entries for justified cases, refactoring dispatches for simplifiable ones. +- **Handler standard** — 83% average. Investigation needed per branch. +- **Test coverage expansion** — 22% average. Structural gap, needs per-branch test scaffolding. - **Cross-platform reliability** — Linux and Windows tested. macOS structurally supported. All paths use `pathlib`, secrets at `~/.secrets/aipass/`. - **Agent agnosticism** — currently focused on [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (hooks for auto-diagnostics, prompt injection, session recovery). But AIPass is designed to not depend on any single provider. `agents.md` and `gemini.md` can bootstrap the system for Codex and Gemini — you lose hooks but keep the core. @@ -93,7 +91,7 @@ Every branch is a citizen — an expert in its domain with its own memories and |--------|------| | `devpulse` | **Start here.** Orchestration hub — coordinates everything, maintains 20 diagnostic tools | | `drone` | AI-friendly CLI — every command is a single-line, non-interactive call | -| `seedgo` | Standards enforcement — 24-standard audit pack, system compliance | +| `seedgo` | Standards enforcement — 34-standard audit pack, system compliance | | `prax` | Logging and monitoring (the only logger in the system) | | `cli` | Terminal display, stderr routing, project commands | | `flow` | Workflow management — FPLANs (execution) and DPLANs (design) | diff --git a/src/aipass/cli/.seedgo/bypass.json b/src/aipass/cli/.seedgo/bypass.json index 7bf91066..33ac20d6 100644 --- a/src/aipass/cli/.seedgo/bypass.json +++ b/src/aipass/cli/.seedgo/bypass.json @@ -9,6 +9,16 @@ "file": "apps/handlers/init/bootstrap.py", "standard": "json_structure", "reason": "Pure Python bootstrap — no branch imports by design" + }, + { + "file": "apps/modules/display.py", + "standard": "silent_catch", + "reason": "Circular import — display.py cannot import prax (prax depends on cli). Silent catches are ImportError guard for optional trigger and __main__ error handler." + }, + { + "file": "apps/handlers/json/json_handler.py", + "standard": "silent_catch", + "reason": "Dependency-free by design — json_handler cannot import prax (circular: json_handler → prax → cli.display → json_handler). Catch regenerates corrupted JSON silently." } ], "notes": { diff --git a/src/aipass/cli/apps/cli.py b/src/aipass/cli/apps/cli.py index 4da48682..4d92bb95 100755 --- a/src/aipass/cli/apps/cli.py +++ b/src/aipass/cli/apps/cli.py @@ -307,6 +307,7 @@ if __name__ == "__main__": try: sys.exit(main()) except KeyboardInterrupt: + logger.warning("CLI interrupted by user") CONSOLE.print("\n[yellow]Operation cancelled[/yellow]") sys.exit(0) except Exception as e: diff --git a/src/aipass/cli/apps/handlers/json/json_handler.py b/src/aipass/cli/apps/handlers/json/json_handler.py index 2b427c2b..bca982fb 100755 --- a/src/aipass/cli/apps/handlers/json/json_handler.py +++ b/src/aipass/cli/apps/handlers/json/json_handler.py @@ -102,7 +102,6 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: return True # If corrupted, fall through to regenerate except Exception: - # If unreadable, fall through to regenerate pass template = load_template(json_type, module_name) diff --git a/src/aipass/cli/apps/modules/display.py b/src/aipass/cli/apps/modules/display.py index 1658b595..7bafd93c 100755 --- a/src/aipass/cli/apps/modules/display.py +++ b/src/aipass/cli/apps/modules/display.py @@ -32,7 +32,7 @@ from rich.columns import Columns from aipass.cli.apps.handlers.json import json_handler # NOTE: Cannot import prax here — circular import (prax depends on cli) -# from aipass.prax import logger +# Silent catches in this file are bypassed via .seedgo/bypass.json # Initialize Rich console (lowercase follows service instance pattern) CONSOLE = Console(force_terminal=True) # Internal constant — force_terminal ensures ANSI colors even when piped @@ -75,7 +75,7 @@ def print_introspection(): CONSOLE.print(" [dim]handlers/display/ (not found)[/dim]") CONSOLE.print() - CONSOLE.print("[dim]Run 'python3 display.py --help' for usage[/dim]") + CONSOLE.print("[dim]Run 'drone @cli display --help' for usage[/dim]") CONSOLE.print() @@ -145,9 +145,9 @@ def print_help(): CONSOLE.print() usage_examples = [ - "[yellow]Module Info:[/yellow]\n [dim]python3 display.py[/dim]\n [dim]drone cli display[/dim]", - "[yellow]Run Demo:[/yellow]\n [dim]python3 display.py demo[/dim]\n [dim]drone cli demo[/dim]", - "[yellow]Show Help:[/yellow]\n [dim]python3 display.py --help[/dim]\n [dim]drone cli display --help[/dim]" + "[yellow]Module Info:[/yellow]\n [dim]drone @cli display[/dim]", + "[yellow]Run Demo:[/yellow]\n [dim]drone @cli display demo[/dim]", + "[yellow]Show Help:[/yellow]\n [dim]drone @cli display --help[/dim]" ] # RICH FORMATTING TIP: Columns creates side-by-side layout @@ -212,7 +212,7 @@ def print_help(): CONSOLE.print() # RICH FORMATTING TIP: Use [bold] for emphasis without color - CONSOLE.print("[bold]TIP:[/bold] Run [green]python3 display.py demo[/green] to see all functions in action!") + CONSOLE.print("[bold]TIP:[/bold] Run [green]drone @cli display demo[/green] to see all functions in action!") CONSOLE.print() CONSOLE.print("─" * 70) CONSOLE.print() @@ -233,6 +233,9 @@ def handle_command(command: str, args: List[str]) -> bool: if not args: print_introspection() return True + if args[0] in ("--help", "-h", "help"): + print_help() + return True if args[0] == "demo": run_demo() return True @@ -431,9 +434,8 @@ if __name__ == "__main__": sys.exit(0) else: CONSOLE.print(f"[red]Unknown command: {command}[/red]") - CONSOLE.print("[dim]Run 'python3 display.py --help' for usage[/dim]") + CONSOLE.print("[dim]Run 'drone @cli display --help' for usage[/dim]") sys.exit(1) except Exception as e: - # Note: Can't use Prax logger here due to circular import (display <- prax <- display) CONSOLE.print(f"[red]Error: {e}[/red]") sys.exit(1) diff --git a/src/aipass/cli/apps/modules/init_project.py b/src/aipass/cli/apps/modules/init_project.py index 8d24c9af..f8980739 100644 --- a/src/aipass/cli/apps/modules/init_project.py +++ b/src/aipass/cli/apps/modules/init_project.py @@ -23,6 +23,7 @@ from typing import List from aipass.cli.apps.handlers.init.bootstrap import init_project from aipass.cli.apps.modules.display import console, success, error, header from aipass.cli.apps.handlers.json import json_handler +from aipass.prax.apps.modules.logger import system_logger as logger # ============================================================================= @@ -185,12 +186,15 @@ def _handle_init(args: List[str]) -> bool: try: result = init_project(target, project_name) except ValueError as exc: + logger.warning("Init validation error: %s", exc) error(str(exc), suggestion="Pass a project name explicitly") sys.exit(1) except FileExistsError as exc: + logger.warning("Init target already exists: %s", exc) error(str(exc), suggestion="Remove the existing file to re-initialize") sys.exit(1) except OSError as exc: + logger.error("Init filesystem error: %s", exc) error(f"Filesystem error: {exc}") sys.exit(1) @@ -326,8 +330,9 @@ if __name__ == "__main__": sys.exit(0) else: console.print(f"[red]Unknown command: {command}[/red]") - console.print("[dim]Run 'python3 init_project.py --help' for usage[/dim]") + console.print("[dim]Run 'drone @cli aipass --help' for usage[/dim]") sys.exit(1) except Exception as e: + logger.error("CLI init_project error: %s", e) console.print(f"[red]Error: {e}[/red]") sys.exit(1) diff --git a/src/aipass/cli/apps/modules/templates.py b/src/aipass/cli/apps/modules/templates.py index baee3523..bbb6f390 100755 --- a/src/aipass/cli/apps/modules/templates.py +++ b/src/aipass/cli/apps/modules/templates.py @@ -58,7 +58,7 @@ def print_introspection(): CONSOLE.print(" [dim]handlers/templates/ (not found)[/dim]") CONSOLE.print() - CONSOLE.print("[dim]Run 'python3 templates.py --help' for usage[/dim]") + CONSOLE.print("[dim]Run 'drone @cli templates --help' for usage[/dim]") CONSOLE.print() @@ -84,10 +84,10 @@ def print_help(): CONSOLE.print("[bold cyan]Usage Examples:[/bold cyan]") CONSOLE.print() CONSOLE.print(" [green]# Show module info[/green]") - CONSOLE.print(" python3 templates.py") + CONSOLE.print(" drone @cli templates") CONSOLE.print() CONSOLE.print(" [green]# Run demo[/green]") - CONSOLE.print(" python3 templates.py demo") + CONSOLE.print(" drone @cli templates demo") CONSOLE.print() CONSOLE.print(" [green]# Via drone[/green]") CONSOLE.print(" drone cli templates") @@ -125,6 +125,9 @@ def handle_command(command: str, args: List[str]) -> bool: if not args: print_introspection() return True + if args[0] in ("--help", "-h", "help"): + print_help() + return True if args[0] == "demo": run_demo() return True @@ -233,5 +236,5 @@ if __name__ == "__main__": sys.exit(0) else: CONSOLE.print(f"[red]Unknown command: {command}[/red]") - CONSOLE.print("[dim]Run 'python3 templates.py --help' for usage[/dim]") + CONSOLE.print("[dim]Run 'drone @cli templates --help' for usage[/dim]") sys.exit(1) diff --git a/src/aipass/daemon/apps/daemon.py b/src/aipass/daemon/apps/daemon.py index 67d19e92..5e389430 100644 --- a/src/aipass/daemon/apps/daemon.py +++ b/src/aipass/daemon/apps/daemon.py @@ -198,6 +198,7 @@ if __name__ == "__main__": try: sys.exit(main()) except KeyboardInterrupt: + logger.warning("DAEMON operation cancelled by user (KeyboardInterrupt)") console.print("\n\nOperation cancelled by user") sys.exit(0) except Exception as e: diff --git a/src/aipass/daemon/apps/daemon_wakeup.py b/src/aipass/daemon/apps/daemon_wakeup.py index 739e69f5..353b79c7 100644 --- a/src/aipass/daemon/apps/daemon_wakeup.py +++ b/src/aipass/daemon/apps/daemon_wakeup.py @@ -28,6 +28,7 @@ import fcntl from pathlib import Path from datetime import datetime +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.cli.apps.modules import console from aipass.daemon.apps.handlers.json import json_handler @@ -103,6 +104,7 @@ def check_inbox() -> dict: with open(INBOX_PATH, "r", encoding="utf-8") as f: inbox_data = json.load(f) except (json.JSONDecodeError, IOError) as e: + logger.warning(f"Failed to read inbox: {e}") log(f"WARNING: Failed to read inbox: {e}") return result @@ -199,7 +201,8 @@ def main() -> int: lock_fd = open(LOCK_FILE, "w") try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except OSError: + except OSError as e: + logger.warning(f"Wakeup lock acquisition failed (another instance running): {e}") log("Another instance already running, skipping.") lock_fd.close() return 0 @@ -220,6 +223,7 @@ def _run_locked() -> int: inbox = check_inbox() log(f"Inbox: {inbox['new_count']} new, {inbox['opened_count']} opened") except Exception as e: + logger.error(f"Unhandled error in check_inbox: {e}", exc_info=True) log(f"CRITICAL: Unhandled error in check_inbox: {e}") return 1 @@ -237,6 +241,7 @@ if __name__ == "__main__": sys.exit(main()) except Exception as e: # Last-resort catch -- never crash silently + logger.error(f"FATAL wakeup exception: {e}", exc_info=True) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") console.print(f"[{timestamp}] FATAL: Unhandled exception: {e}") sys.exit(1) diff --git a/src/aipass/daemon/apps/handlers/actions/actions_registry.py b/src/aipass/daemon/apps/handlers/actions/actions_registry.py index 2aa95d7b..516d1598 100644 --- a/src/aipass/daemon/apps/handlers/actions/actions_registry.py +++ b/src/aipass/daemon/apps/handlers/actions/actions_registry.py @@ -262,7 +262,8 @@ def is_action_due(action: dict) -> bool: target_time = action.get("time", "00:00") try: target_h, target_m = map(int, target_time.split(":")) - except (ValueError, AttributeError): + except (ValueError, AttributeError) as e: + logger.info("[actions_registry] Daily time parse failed for %r: %s", target_time, e) return False # Fuzzy 15-minute window (cron may not fire at exact minute) current_minutes = now.hour * 60 + now.minute @@ -277,15 +278,16 @@ def is_action_due(action: dict) -> bool: last_dt = datetime.fromisoformat(last_run) if last_dt.date() == now.date(): return False - except (ValueError, TypeError): - pass + except (ValueError, TypeError) as e: + logger.info("[actions_registry] Daily last_run parse failed: %s", e) return True elif schedule_type == "hourly": target_m_str = action.get("time", "0") try: target_m = int(target_m_str) - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.info("[actions_registry] Hourly time parse failed for %r: %s", target_m_str, e) return False # Fuzzy 15-minute window (cron may not fire at exact minute) minutes_diff = abs(now.minute - target_m) @@ -298,8 +300,8 @@ def is_action_due(action: dict) -> bool: last_dt = datetime.fromisoformat(last_run) if last_dt.hour == now.hour and last_dt.date() == now.date(): return False - except (ValueError, TypeError): - pass + except (ValueError, TypeError) as e: + logger.info("[actions_registry] Hourly last_run parse failed: %s", e) return True elif schedule_type == "interval": @@ -311,7 +313,8 @@ def is_action_due(action: dict) -> bool: last_dt = datetime.fromisoformat(last_run) elapsed = (now - last_dt).total_seconds() / 60 return elapsed >= interval - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.info("[actions_registry] Interval last_run parse failed: %s", e) return True elif schedule_type == "once": @@ -321,7 +324,8 @@ def is_action_due(action: dict) -> bool: try: due_dt = datetime.fromisoformat(due_date).date() if "T" in due_date else datetime.strptime(due_date, "%Y-%m-%d").date() return now.date() >= due_dt - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.info("[actions_registry] Once due_date parse failed for %r: %s", due_date, e) return False return False @@ -336,7 +340,8 @@ def calc_next_run(action: dict) -> Optional[str]: target_time = action.get("time", "00:00") try: target_h, target_m = map(int, target_time.split(":")) - except (ValueError, AttributeError): + except (ValueError, AttributeError) as e: + logger.info("[actions_registry] calc_next_run daily time parse failed: %s", e) return None next_dt = now.replace(hour=target_h, minute=target_m, second=0, microsecond=0) if next_dt <= now: @@ -347,7 +352,8 @@ def calc_next_run(action: dict) -> Optional[str]: target_m_str = action.get("time", "0") try: target_m = int(target_m_str) - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.info("[actions_registry] calc_next_run hourly time parse failed: %s", e) return None next_dt = now.replace(minute=target_m, second=0, microsecond=0) if next_dt <= now: @@ -362,7 +368,8 @@ def calc_next_run(action: dict) -> Optional[str]: try: last_dt = datetime.fromisoformat(last_run) return (last_dt + timedelta(minutes=interval)).isoformat() - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.info("[actions_registry] calc_next_run interval last_run parse failed: %s", e) return now.isoformat() elif schedule_type == "once": @@ -393,7 +400,8 @@ def next_due_str(action: dict) -> str: if next_dt <= datetime.now(): return "now" return next_dt.strftime("%H:%M") - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.info("[actions_registry] next_due_str interval last_run parse failed: %s", e) return "now" return "now" elif schedule_type == "once": @@ -429,8 +437,8 @@ def migrate_plugins() -> int: if last_run_file.exists(): try: last_run_map = json.loads(last_run_file.read_text(encoding="utf-8")) - except (json.JSONDecodeError, OSError): - pass + except (json.JSONDecodeError, OSError) as e: + logger.warning("[actions_registry] Failed to load last_run.json: %s", e) # Discover plugins migrated = 0 diff --git a/src/aipass/daemon/apps/handlers/json/json_handler.py b/src/aipass/daemon/apps/handlers/json/json_handler.py index 96d4143a..3e8a2318 100644 --- a/src/aipass/daemon/apps/handlers/json/json_handler.py +++ b/src/aipass/daemon/apps/handlers/json/json_handler.py @@ -18,6 +18,8 @@ from datetime import datetime from typing import Dict, List, Any, Optional import inspect +from aipass.prax import logger + # Constants _DAEMON_ROOT = Path(__file__).resolve().parents[3] # src/aipass/daemon/ JSON_DIR = _DAEMON_ROOT / "daemon_json" @@ -101,10 +103,10 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: if validate_json_structure(data, json_type): return True - except json.JSONDecodeError: - pass # File corrupted, regenerate - except OSError: - pass # File unreadable, regenerate + except json.JSONDecodeError as e: + logger.warning("[json_handler] Corrupted JSON file %s, regenerating: %s", json_path.name, e) + except OSError as e: + logger.warning("[json_handler] Unreadable JSON file %s, regenerating: %s", json_path.name, e) template = load_template(json_type, module_name) diff --git a/src/aipass/daemon/apps/handlers/monitoring/activity_collector.py b/src/aipass/daemon/apps/handlers/monitoring/activity_collector.py index 3dd556ed..e41884ff 100644 --- a/src/aipass/daemon/apps/handlers/monitoring/activity_collector.py +++ b/src/aipass/daemon/apps/handlers/monitoring/activity_collector.py @@ -20,6 +20,7 @@ from pathlib import Path from datetime import datetime, timedelta from typing import Dict, List, Any, Optional +from aipass.prax import logger from aipass.daemon.apps.handlers.json import json_handler @@ -49,7 +50,8 @@ def load_branch_registry() -> Dict[str, Any]: try: with open(REGISTRY_PATH, 'r', encoding='utf-8') as f: return json.load(f) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to load branch registry %s: %s", REGISTRY_PATH, e) return {"metadata": {}, "branches": []} @@ -93,8 +95,8 @@ def _get_file_mtime(file_path: Path) -> Optional[datetime]: try: if file_path.exists(): return datetime.fromtimestamp(file_path.stat().st_mtime) - except OSError: - pass + except OSError as e: + logger.warning("Failed to get mtime for %s: %s", file_path, e) return None @@ -188,10 +190,10 @@ def _scan_directory_files( memory_files.append(file_info) elif item.suffix == CODE_FILE_EXTENSION: code_files.append(file_info) - except PermissionError: - pass - except OSError: - pass + except PermissionError as e: + logger.warning("Permission denied scanning %s: %s", path, e) + except OSError as e: + logger.warning("OS error scanning %s: %s", path, e) scan_recursive(directory) diff --git a/src/aipass/daemon/apps/handlers/monitoring/memory_health.py b/src/aipass/daemon/apps/handlers/monitoring/memory_health.py index cf8c2a1f..4191373e 100644 --- a/src/aipass/daemon/apps/handlers/monitoring/memory_health.py +++ b/src/aipass/daemon/apps/handlers/monitoring/memory_health.py @@ -18,6 +18,7 @@ from pathlib import Path from datetime import datetime from typing import Dict, List, Any +from aipass.prax import logger from aipass.daemon.apps.handlers.json import json_handler @@ -147,6 +148,7 @@ def validate_memory_structure( with open(path, 'r', encoding='utf-8') as f: data = json.load(f) except json.JSONDecodeError as e: + logger.warning("Invalid JSON in memory file %s: %s", file_path, e) return { "valid": False, "has_metadata": False, @@ -155,6 +157,7 @@ def validate_memory_structure( "metadata_fields": [], } except OSError as e: + logger.warning("Cannot read memory file %s: %s", file_path, e) return { "valid": False, "has_metadata": False, @@ -258,6 +261,7 @@ def check_freshness( "message": message, } except OSError as e: + logger.error("Cannot read file stats for %s: %s", file_path, e) return { "exists": True, "last_modified": None, diff --git a/src/aipass/daemon/apps/handlers/monitoring/red_flag_detector.py b/src/aipass/daemon/apps/handlers/monitoring/red_flag_detector.py index 3324908e..ab059cbc 100644 --- a/src/aipass/daemon/apps/handlers/monitoring/red_flag_detector.py +++ b/src/aipass/daemon/apps/handlers/monitoring/red_flag_detector.py @@ -26,6 +26,7 @@ OK Conditions: from datetime import datetime, timedelta from typing import Dict, List, Any, Optional +from aipass.prax import logger from aipass.daemon.apps.handlers.json import json_handler from aipass.daemon.apps.handlers.monitoring import activity_collector @@ -55,7 +56,8 @@ def _parse_iso_datetime(iso_string: str) -> Optional[datetime]: return datetime.fromisoformat(iso_string) else: return datetime.fromisoformat(iso_string) - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.warning("Failed to parse ISO datetime %s: %s", iso_string, e) return None @@ -147,6 +149,7 @@ def get_branch_status( branch_name, branch_path, since_timestamp ) except Exception as e: + logger.error("Failed to scan branch %s: %s", branch_name, e) result["status"] = STATUS_ERROR result["reason"] = f"Failed to scan branch: {str(e)}" return result diff --git a/src/aipass/daemon/apps/handlers/monitoring/report_generator.py b/src/aipass/daemon/apps/handlers/monitoring/report_generator.py index e62b59d1..001c635b 100644 --- a/src/aipass/daemon/apps/handlers/monitoring/report_generator.py +++ b/src/aipass/daemon/apps/handlers/monitoring/report_generator.py @@ -66,7 +66,8 @@ def _format_time_ago(iso_timestamp: Optional[str]) -> str: else: days = int(hours / 24) return f"{days}d ago" - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.warning("Failed to parse timestamp %s: %s", iso_timestamp, e) return "unknown" diff --git a/src/aipass/daemon/apps/handlers/schedule/task_registry.py b/src/aipass/daemon/apps/handlers/schedule/task_registry.py index d7b3078c..c90ee018 100644 --- a/src/aipass/daemon/apps/handlers/schedule/task_registry.py +++ b/src/aipass/daemon/apps/handlers/schedule/task_registry.py @@ -21,6 +21,7 @@ from datetime import datetime, timedelta from typing import Dict, List, Any, Optional import re +from aipass.prax import logger from aipass.daemon.apps.handlers.json import json_handler # ============================================= @@ -71,7 +72,8 @@ def load_tasks() -> List[Dict[str, Any]]: with open(SCHEDULE_JSON_PATH, 'r', encoding='utf-8') as f: data = json.load(f) return data.get("tasks", []) - except (json.JSONDecodeError, IOError): + except (json.JSONDecodeError, IOError) as e: + logger.error("[task_registry] Failed to load schedule.json: %s", e) return [] @@ -92,7 +94,8 @@ def save_tasks(tasks: List[Dict[str, Any]]) -> bool: with open(SCHEDULE_JSON_PATH, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return True - except IOError: + except IOError as e: + logger.error("[task_registry] Failed to save schedule.json: %s", e) return False @@ -319,8 +322,9 @@ def recover_stale_dispatches(max_age_minutes: int = 5) -> int: task["status"] = "pending" task.pop("dispatch_started", None) recovered += 1 - except ValueError: + except ValueError as e: # Invalid timestamp, reset anyway + logger.warning("[task_registry] Invalid dispatch_started timestamp, resetting task: %s", e) task["status"] = "pending" task.pop("dispatch_started", None) recovered += 1 @@ -422,12 +426,14 @@ def process_due_tasks_batch( recovered = recover_stale_dispatches(max_age_minutes=stale_max_age) results["recovered"] = recovered except Exception as e: + logger.warning("[task_registry] Stale dispatch recovery failed: %s", e) results["errors"].append(f"Stale recovery: {e}") # Get due tasks try: due_tasks = get_due_tasks() except Exception as e: + logger.error("[task_registry] Failed to load due tasks: %s", e) results["errors"].append(f"Load tasks: {e}") return results @@ -453,6 +459,7 @@ def process_due_tasks_batch( try: mark_dispatching(task_id) except Exception as e: + logger.error("[task_registry] Failed to mark task %s as dispatching: %s", task_id[:8], e) results["errors"].append(f"Mark dispatching {task_id[:8]}: {e}") results["failed"] += 1 task_result["status"] = "error" @@ -497,10 +504,11 @@ def process_due_tasks_batch( results["errors"].append(f"Email failed: {task_id[:8]} -> {recipient}") except Exception as e: + logger.error("[task_registry] Email dispatch error for task %s: %s", task_id[:8], e) try: mark_pending(task_id) - except Exception: - pass + except Exception as pending_err: + logger.error("[task_registry] Failed to reset task %s to pending: %s", task_id[:8], pending_err) results["failed"] += 1 task_result["status"] = "error" task_result["error"] = str(e) @@ -540,12 +548,14 @@ if __name__ == "__main__": result = parse_due_date(d) console.print(f" {d} -> {result}") except ValueError as e: + logger.warning("Date parse test failed for %s: %s", d, e) console.print(f" {d} -> [red]ERROR: {e}[/red]") # Test invalid date try: parse_due_date("invalid") except ValueError as e: + logger.info("Expected parse failure for 'invalid': %s", e) console.print(f" invalid -> [green]Correctly raised: {e}[/green]") console.print() diff --git a/src/aipass/daemon/apps/handlers/update/data_loader.py b/src/aipass/daemon/apps/handlers/update/data_loader.py index 356d820f..7638dc26 100644 --- a/src/aipass/daemon/apps/handlers/update/data_loader.py +++ b/src/aipass/daemon/apps/handlers/update/data_loader.py @@ -14,6 +14,7 @@ import json from pathlib import Path from typing import Dict, Any, List +from aipass.prax import logger from aipass.daemon.apps.handlers.json import json_handler # ============================================= @@ -37,7 +38,8 @@ def load_inbox() -> Dict[str, Any]: try: with open(INBOX_PATH, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as e: + logger.error("[data_loader] Failed to load inbox.json: %s", e) return {"messages": [], "total_messages": 0, "unread_count": 0} @@ -49,7 +51,8 @@ def load_local() -> Dict[str, Any]: try: with open(LOCAL_PATH, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as e: + logger.error("[data_loader] Failed to load DAEMON.local.json: %s", e) return {"sessions": [], "active_tasks": {}} diff --git a/src/aipass/daemon/apps/modules/actions.py b/src/aipass/daemon/apps/modules/actions.py index 5bc38c5a..1272bff3 100644 --- a/src/aipass/daemon/apps/modules/actions.py +++ b/src/aipass/daemon/apps/modules/actions.py @@ -328,6 +328,7 @@ def _handle_set_schedule(args: List[str]) -> bool: try: interval_minutes = int(args[3]) except ValueError: + logger.warning("Invalid interval minutes value: %s", args[3]) _error(f"Invalid interval minutes: {args[3]}") return False else: @@ -421,12 +422,14 @@ def _parse_date(date_str: str) -> str: days = int(date_str[:-1]) return (datetime.now() + timedelta(days=days)).strftime("%Y-%m-%d") except ValueError: + logger.warning("Invalid relative day format: %s", date_str) return "" elif date_str.endswith("w"): try: weeks = int(date_str[:-1]) return (datetime.now() + timedelta(weeks=weeks)).strftime("%Y-%m-%d") except ValueError: + logger.warning("Invalid relative week format: %s", date_str) return "" # ISO date @@ -434,6 +437,7 @@ def _parse_date(date_str: str) -> str: datetime.strptime(date_str, "%Y-%m-%d") return date_str except ValueError: + logger.warning("Invalid ISO date format: %s", date_str) return "" diff --git a/src/aipass/daemon/apps/modules/activity_report.py b/src/aipass/daemon/apps/modules/activity_report.py index d2cbb0a4..6dfa1a02 100644 --- a/src/aipass/daemon/apps/modules/activity_report.py +++ b/src/aipass/daemon/apps/modules/activity_report.py @@ -146,8 +146,8 @@ def _parse_hours_arg(args: List[str]) -> float: if args[i] in ('--hours', '-t') and i + 1 < len(args): try: hours = float(args[i + 1]) - except ValueError: - pass + except ValueError as e: + logger.warning("Invalid --hours value '%s': %s", args[i + 1], e) i += 2 else: i += 1 diff --git a/src/aipass/daemon/apps/modules/schedule.py b/src/aipass/daemon/apps/modules/schedule.py index b402e87c..d774ccc4 100644 --- a/src/aipass/daemon/apps/modules/schedule.py +++ b/src/aipass/daemon/apps/modules/schedule.py @@ -50,6 +50,7 @@ except ImportError: FILELOCK_AVAILABLE = False FileLock = None # type: ignore[assignment,misc] Timeout = None # type: ignore[assignment,misc] + logger.info("Optional: filelock not available") # Email integration via drone subprocess import subprocess @@ -63,7 +64,8 @@ def _send_email_via_drone(to_branch, subject, message, from_branch='@daemon', try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=DRONE_SUBPROCESS_TIMEOUT) return result.returncode == 0 - except (subprocess.SubprocessError, OSError): + except (subprocess.SubprocessError, OSError) as e: + logger.warning("Drone email subprocess failed: %s", e) return False AI_MAIL_AVAILABLE = True @@ -184,6 +186,7 @@ def _handle_create(args: List[str]) -> bool: try: parsed = parser.parse_args(args) except SystemExit: + logger.warning("Invalid arguments for schedule create") _error('Usage: schedule create "task" --due --to @branch [--message "details"]') return False @@ -280,6 +283,7 @@ def _handle_run_due(_args: List[str]) -> bool: with lock.acquire(timeout=LOCK_ACQUIRE_TIMEOUT): return _process_due_tasks() except Timeout: # type: ignore[misc] + logger.warning("Schedule run-due already in progress, skipping") console.print("[dim]Schedule run-due already in progress, skipping.[/dim]") return True diff --git a/src/aipass/daemon/apps/modules/scheduler_ops.py b/src/aipass/daemon/apps/modules/scheduler_ops.py index 12976928..6dafb830 100644 --- a/src/aipass/daemon/apps/modules/scheduler_ops.py +++ b/src/aipass/daemon/apps/modules/scheduler_ops.py @@ -13,6 +13,8 @@ Provides a clean module-layer interface over handler functions used by scheduler_cron.py. """ +from aipass.prax import logger + from aipass.daemon.apps.handlers.json import json_handler try: @@ -20,6 +22,7 @@ try: except ImportError: from rich.console import Console console = Console() + logger.info("Optional: aipass.cli.apps.modules.display not available, using rich.console fallback") # ============================================= # TASK REGISTRY @@ -41,6 +44,7 @@ except ImportError: mark_completed = None # type: ignore[assignment] mark_pending = None # type: ignore[assignment] recover_stale_dispatches = None # type: ignore[assignment] + logger.info("Optional: task_registry not available") # ============================================= # ACTION REGISTRY (DPLAN-043) @@ -64,6 +68,7 @@ except ImportError: mark_reminder_completed = None # type: ignore[assignment] migrate_plugins = None # type: ignore[assignment] next_due_str = None # type: ignore[assignment] + logger.info("Optional: actions_registry not available") # ============================================= diff --git a/src/aipass/daemon/apps/modules/wakeup_ops.py b/src/aipass/daemon/apps/modules/wakeup_ops.py index 4c606e75..d0231278 100644 --- a/src/aipass/daemon/apps/modules/wakeup_ops.py +++ b/src/aipass/daemon/apps/modules/wakeup_ops.py @@ -13,6 +13,8 @@ Provides a clean module-layer interface over handler functions used by daemon_wakeup.py. """ +from aipass.prax import logger + from aipass.daemon.apps.handlers.json import json_handler try: @@ -20,6 +22,7 @@ try: except ImportError: from rich.console import Console console = Console() + logger.info("Optional: aipass.cli.apps.modules.display not available, using rich.console fallback") # ============================================= # INTROSPECTION diff --git a/src/aipass/daemon/apps/plugins/__init__.py b/src/aipass/daemon/apps/plugins/__init__.py index 698c0172..4bd742e8 100644 --- a/src/aipass/daemon/apps/plugins/__init__.py +++ b/src/aipass/daemon/apps/plugins/__init__.py @@ -54,6 +54,8 @@ Naming Convention: import importlib from pathlib import Path +from aipass.prax import logger + def discover_plugins() -> list: """ @@ -88,7 +90,8 @@ def discover_plugins() -> list: "config": config, "file": str(file_path), }) - except Exception: + except Exception as e: + logger.warning("Failed to load plugin %s: %s", module_name, e) continue return plugins diff --git a/src/aipass/daemon/apps/plugins/community_rotation.py b/src/aipass/daemon/apps/plugins/community_rotation.py index 50dcb360..5dbb45cd 100644 --- a/src/aipass/daemon/apps/plugins/community_rotation.py +++ b/src/aipass/daemon/apps/plugins/community_rotation.py @@ -124,7 +124,8 @@ def _load_rotation_state() -> int: with open(ROTATION_STATE_FILE, 'r', encoding='utf-8') as f: data = json.load(f) return data.get("last_index", -1) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning("[community_rotation] Failed to load rotation state: %s", e) return -1 @@ -421,12 +422,14 @@ def run() -> dict: } except subprocess.TimeoutExpired: + logger.error("[community_rotation] Wake script timed out for %s", target["name"]) return { "status": "failed", "branch": target["email"], "error": "wake script timed out (30s)", } except Exception as e: + logger.error("[community_rotation] Wake script failed for %s: %s", target["name"], e) return { "status": "failed", "branch": target["email"], diff --git a/src/aipass/daemon/apps/scheduler_cron.py b/src/aipass/daemon/apps/scheduler_cron.py index 8ca550b0..08675e2e 100755 --- a/src/aipass/daemon/apps/scheduler_cron.py +++ b/src/aipass/daemon/apps/scheduler_cron.py @@ -52,7 +52,8 @@ try: recover_stale_dispatches, TASK_REGISTRY_AVAILABLE, ) -except ImportError: +except ImportError as e: + logger.info(f"Optional dependency not available: scheduler_ops task registry ({e})") TASK_REGISTRY_AVAILABLE = False get_due_tasks = None mark_dispatching = None @@ -70,7 +71,8 @@ def _send_email_via_drone(to_branch, subject, message, from_branch='@daemon', try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) return result.returncode == 0 - except (subprocess.SubprocessError, OSError): + except (subprocess.SubprocessError, OSError) as e: + logger.warning(f"Drone email subprocess failed: {e}") return False AI_MAIL_AVAILABLE = True @@ -80,7 +82,8 @@ send_email_direct = _send_email_via_drone try: from aipass.daemon.apps.plugins import discover_plugins PLUGINS_AVAILABLE = True -except ImportError: +except ImportError as e: + logger.info(f"Optional dependency not available: discover_plugins ({e})") PLUGINS_AVAILABLE = False discover_plugins = None @@ -95,7 +98,8 @@ try: next_due_str, ACTION_REGISTRY_AVAILABLE, ) -except ImportError: +except ImportError as e: + logger.info(f"Optional dependency not available: scheduler_ops action registry ({e})") ACTION_REGISTRY_AVAILABLE = False load_registry = None is_action_due = None @@ -190,6 +194,7 @@ def process_due_tasks() -> dict: if recovered: log(f"Recovered {recovered} stale dispatch(es)") except Exception as e: + logger.warning(f"Failed to recover stale dispatches: {e}") log(f"WARNING: Failed to recover stale dispatches: {e}") results["errors"].append(f"Stale recovery: {e}") @@ -197,6 +202,7 @@ def process_due_tasks() -> dict: try: due_tasks = get_due_tasks() # type: ignore[misc] except Exception as e: + logger.error(f"Failed to load due tasks: {e}") log(f"ERROR: Failed to load due tasks: {e}") results["errors"].append(f"Load tasks: {e}") return results @@ -222,6 +228,7 @@ def process_due_tasks() -> dict: try: mark_dispatching(task_id) # type: ignore[misc] except Exception as e: + logger.warning(f"Failed to mark dispatching {task_id[:8]}: {e}") log(f"WARNING: Failed to mark dispatching {task_id[:8]}: {e}") results["errors"].append(f"Mark dispatching {task_id[:8]}: {e}") results["failed"] += 1 @@ -264,8 +271,9 @@ def process_due_tasks() -> dict: # Reset to pending for retry on next run try: mark_pending(task_id) # type: ignore[misc] - except Exception: - pass # Best effort reset + except Exception as reset_err: + logger.warning(f"Best-effort reset to pending failed for {task_id[:8]}: {reset_err}") + logger.error(f"Exception sending to {recipient}: {e}") log(f"ERROR: Exception sending to {recipient}: {e}") results["failed"] += 1 results["errors"].append(f"Email error {task_id[:8]}: {e}") @@ -286,7 +294,8 @@ def _load_last_run() -> dict: if PLUGIN_LAST_RUN_FILE.exists(): try: return json.loads(PLUGIN_LAST_RUN_FILE.read_text()) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning(f"Failed to load plugin last-run file, using empty defaults: {e}") return {} return {} @@ -425,6 +434,7 @@ def process_plugins() -> dict: try: plugins = discover_plugins() # type: ignore[misc] except Exception as e: + logger.error(f"Plugin discovery failed: {e}") log(f"PLUGIN: Discovery failed: {e}") results["errors"].append(f"Plugin discovery: {e}") return results @@ -479,6 +489,7 @@ def process_plugins() -> dict: results["failed"] += 1 results["errors"].append(f"Plugin {name} self-dispatch: {error_msg}") except Exception as e: + logger.error(f"Plugin {name} self-dispatch error: {e}") log(f"PLUGIN: {name} - self-dispatch error: {e}") results["failed"] += 1 results["errors"].append(f"Plugin {name}: {e}") @@ -523,10 +534,12 @@ def process_plugins() -> dict: results["errors"].append(f"Plugin {name} wake rc={result.returncode}") except subprocess.TimeoutExpired: + logger.warning(f"Plugin {name} wake script timed out (30s)") log(f"PLUGIN: {name} - wake script timed out (30s)") results["failed"] += 1 results["errors"].append(f"Plugin {name} wake timeout") except Exception as e: + logger.error(f"Plugin {name} error: {e}") log(f"PLUGIN: {name} - error: {e}") results["failed"] += 1 results["errors"].append(f"Plugin {name}: {e}") @@ -538,6 +551,7 @@ def process_plugins() -> dict: try: _save_last_run(last_run_map) except Exception as e: + logger.warning(f"Failed to save plugin last_run timestamps: {e}") log(f"PLUGIN: Failed to save last_run: {e}") results["errors"].append(f"Save last_run: {e}") @@ -580,6 +594,7 @@ def _dispatch_action(action: dict) -> dict: try: module = importlib.import_module(f".plugins.{plugin_file}", package=__package__) except Exception as e: + logger.error(f"Action {name} failed to import plugin {plugin_file}: {e}") log(f"ACTION: {name} - failed to import plugin {plugin_file}: {e}") return {"status": "failed", "branch": target, "error": str(e)} @@ -598,6 +613,7 @@ def _dispatch_action(action: dict) -> dict: log(f"ACTION: {name} - self-dispatch failed: {error_msg}") return {"status": "failed", "branch": target, "error": error_msg} except Exception as e: + logger.error(f"Action {name} self-dispatch error: {e}") log(f"ACTION: {name} - self-dispatch error: {e}") return {"status": "failed", "branch": target, "error": str(e)} @@ -613,6 +629,7 @@ def _dispatch_action(action: dict) -> dict: return {"status": "ok", "branch": target} return {"status": "failed", "branch": target, "error": f"run() returned {run_status}"} except Exception as e: + logger.warning(f"Action {name} plugin run() error: {e}") log(f"ACTION: {name} - plugin run() error: {e}") # Continue to wake script dispatch anyway @@ -639,6 +656,7 @@ def _dispatch_action(action: dict) -> dict: log(f"ACTION: {name} - reminder email failed") return {"status": "failed", "branch": target, "error": "email send returned False"} except Exception as e: + logger.error(f"Action {name} reminder error: {e}") log(f"ACTION: {name} - reminder error: {e}") return {"status": "failed", "branch": target, "error": str(e)} @@ -671,9 +689,11 @@ def _dispatch_action(action: dict) -> dict: log(f"ACTION: {name} - wake script failed (rc={result.returncode}): {stderr_snippet}") return {"status": "failed", "branch": target, "error": f"wake rc={result.returncode}"} except subprocess.TimeoutExpired: + logger.warning(f"Action {name} wake script timed out (30s)") log(f"ACTION: {name} - wake script timed out (30s)") return {"status": "failed", "branch": target, "error": "wake timeout"} except Exception as e: + logger.error(f"Action {name} dispatch error: {e}") log(f"ACTION: {name} - dispatch error: {e}") return {"status": "failed", "branch": target, "error": str(e)} @@ -709,6 +729,7 @@ def process_actions() -> dict: try: _ensure_registry() except Exception as e: + logger.warning(f"Action registry migration error: {e}") log(f"ACTION: Migration error: {e}") results["errors"].append(f"Migration: {e}") @@ -716,6 +737,7 @@ def process_actions() -> dict: try: registry = load_registry() # type: ignore[misc] except Exception as e: + logger.error(f"Failed to load action registry: {e}") log(f"ACTION: Failed to load registry: {e}") results["errors"].append(f"Load registry: {e}") return results @@ -806,7 +828,8 @@ def main() -> int: lock_fd = open(LOCK_FILE, "w", encoding="utf-8") try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except OSError: + except OSError as e: + logger.warning(f"Scheduler lock acquisition failed (another instance running): {e}") log("Another instance already running, skipping.") lock_fd.close() return 0 @@ -826,6 +849,7 @@ def _run_locked() -> int: try: results = process_due_tasks() except Exception as e: + logger.error(f"Unhandled error in process_due_tasks: {e}", exc_info=True) log(f"CRITICAL: Unhandled error in process_due_tasks: {e}") return 1 @@ -837,6 +861,7 @@ def _run_locked() -> int: try: action_results = process_actions() except Exception as e: + logger.warning(f"Unhandled error in process_actions: {e}") log(f"WARNING: Unhandled error in process_actions: {e}") action_results["errors"].append(f"Action processing: {e}") @@ -889,6 +914,7 @@ if __name__ == "__main__": sys.exit(main()) except Exception as e: # Last-resort catch -- never crash silently + logger.error(f"FATAL scheduler_cron exception: {e}", exc_info=True) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") console.print(f"[{timestamp}] FATAL: Unhandled exception: {e}") sys.exit(1) diff --git a/src/aipass/drone/apps/drone.py b/src/aipass/drone/apps/drone.py index 37368a8b..5bf1eca7 100644 --- a/src/aipass/drone/apps/drone.py +++ b/src/aipass/drone/apps/drone.py @@ -165,6 +165,7 @@ def _handle_module(name: str, args: List[str]) -> int: try: result = route_module_command(name, command, cmd_args) except (ImportError, AttributeError) as exc: + logger.error("Module @%s not available: %s", name, exc) err_console.print(f"drone: module @{name} is registered but not available: {exc}") return 1 @@ -263,6 +264,7 @@ def _handle_custom_command(args: list[str]) -> int: interactive=interactive, ) except (BranchNotFoundError, CommandExecutionError, RegistryError) as exc: + logger.warning("Custom command failed for target %s: %s", target, exc) err_console.print(f"drone: {exc}") return 1 @@ -300,6 +302,7 @@ def _handle_target(args: List[str]) -> int: try: result = route_command(target) except (BranchNotFoundError, CommandExecutionError, RegistryError) as exc: + logger.warning("Introspection failed for %s: %s", target, exc) err_console.print(f"drone: {exc}") return 1 if result.stdout: @@ -317,6 +320,7 @@ def _handle_target(args: List[str]) -> int: else: console.print(f"No help available for {target}.") except (BranchNotFoundError, CommandExecutionError, RegistryError) as exc: + logger.warning("Help lookup failed for %s: %s", target, exc) err_console.print(f"drone: {exc}") return 1 return 0 @@ -335,6 +339,7 @@ def _handle_target(args: List[str]) -> int: interactive=interactive, ) except (BranchNotFoundError, CommandExecutionError, RegistryError) as exc: + logger.warning("Command routing failed for %s %s: %s", target, command, exc) err_console.print(f"drone: {exc}") return 1 @@ -358,6 +363,7 @@ def main() -> int: try: show_introspection() except RegistryError as exc: + logger.warning("Registry error during introspection: %s", exc) err_console.print(f"drone: {exc}") return 1 return 0 @@ -379,6 +385,7 @@ def main() -> int: try: return _handle_systems() except RegistryError as exc: + logger.warning("Registry error during systems listing: %s", exc) err_console.print(f"drone: {exc}") return 1 diff --git a/src/aipass/drone/apps/handlers/discovery_handler.py b/src/aipass/drone/apps/handlers/discovery_handler.py index 326a1341..051a8c70 100644 --- a/src/aipass/drone/apps/handlers/discovery_handler.py +++ b/src/aipass/drone/apps/handlers/discovery_handler.py @@ -111,8 +111,8 @@ def discover_modules(branch_path: str, branch_name: str) -> List[str]: if commands: json_handler.log_operation("discover_modules", {"branch": branch_name, "count": len(commands)}) return commands - except (subprocess.TimeoutExpired, OSError): - pass + except (subprocess.TimeoutExpired, OSError) as exc: + logger.warning("discover_modules: --help failed for '%s': %s", branch_name, exc) modules = scan_modules_directory(branch_path) json_handler.log_operation("discover_modules", {"branch": branch_name, "count": len(modules), "source": "scan"}) diff --git a/src/aipass/drone/apps/handlers/git/lock_handler.py b/src/aipass/drone/apps/handlers/git/lock_handler.py index bec5254f..8b268415 100644 --- a/src/aipass/drone/apps/handlers/git/lock_handler.py +++ b/src/aipass/drone/apps/handlers/git/lock_handler.py @@ -47,6 +47,7 @@ def find_repo_root() -> Path: if result.returncode == 0 and result.stdout.strip(): return Path(result.stdout.strip()) except (OSError, subprocess.SubprocessError): + logger.warning("find_repo_root: git rev-parse fallback failed, using CWD") pass return cwd @@ -125,6 +126,7 @@ def release_lock(force: bool = False) -> dict: logger.info("Lock released (force=%s)", force) return {"success": True, "message": "Lock released"} except OSError as exc: + logger.warning("release_lock: failed to remove lock file: %s", exc) return {"success": False, "message": f"Failed to release lock: {exc}"} @@ -176,6 +178,7 @@ def check_lock_status() -> dict: age_seconds = (datetime.now(timezone.utc) - start_time).total_seconds() stale = age_seconds > _STALE_THRESHOLD_SECONDS except (ValueError, TypeError): + logger.warning("check_lock_status: could not parse lock start time: %s", started) pass # Check if PID is still alive (orphan detection) @@ -187,6 +190,7 @@ def check_lock_status() -> dict: orphaned = True except PermissionError: # Process exists but we can't signal it — not orphaned + logger.warning("check_lock_status: PID %d exists but permission denied for signal check", pid) pass status = "active" @@ -226,4 +230,5 @@ def _read_lock_file(lock_path: Path) -> dict | None: content = lock_path.read_text(encoding="utf-8") return json.loads(content) except (OSError, json.JSONDecodeError): + logger.warning("_read_lock_file: could not read or parse lock file %s", lock_path) return None diff --git a/src/aipass/drone/apps/handlers/git/pr_handler.py b/src/aipass/drone/apps/handlers/git/pr_handler.py index 729fd1e6..2784d797 100644 --- a/src/aipass/drone/apps/handlers/git/pr_handler.py +++ b/src/aipass/drone/apps/handlers/git/pr_handler.py @@ -107,6 +107,7 @@ def create_pr(branch_name: str, description: str, branch_dir: Path) -> dict: try: rel_dir = branch_dir.resolve().relative_to(repo_root.resolve()) except ValueError: + logger.warning("create_pr: branch_dir %s not relative to repo root %s, using absolute", branch_dir, repo_root) rel_dir = branch_dir add_result = subprocess.run( diff --git a/src/aipass/drone/apps/handlers/git/status_handler.py b/src/aipass/drone/apps/handlers/git/status_handler.py index a99d30c3..f2a9726a 100644 --- a/src/aipass/drone/apps/handlers/git/status_handler.py +++ b/src/aipass/drone/apps/handlers/git/status_handler.py @@ -54,6 +54,7 @@ def get_branch_status(branch_dir: Path) -> dict: try: rel_dir = branch_dir.resolve().relative_to(repo_root.resolve()) except ValueError: + logger.warning("get_branch_status: branch_dir %s not relative to repo root %s, using absolute", branch_dir, repo_root) rel_dir = branch_dir rel_prefix = str(rel_dir) + "/" diff --git a/src/aipass/drone/apps/handlers/json/json_handler.py b/src/aipass/drone/apps/handlers/json/json_handler.py index 709d6b68..80fabed7 100644 --- a/src/aipass/drone/apps/handlers/json/json_handler.py +++ b/src/aipass/drone/apps/handlers/json/json_handler.py @@ -14,6 +14,8 @@ from datetime import datetime from pathlib import Path from typing import Any +from aipass.prax import logger + # --------------------------------------------------------------------------- # Infrastructure — auto-detect branch root from file location # json_handler.py -> json/ -> handlers/ -> apps/ -> drone/ @@ -164,8 +166,8 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: if validate_json_structure(data, json_type): return True # Corrupted — fall through to regenerate - except Exception: # noqa: BLE001 - pass # Unreadable — fall through to regenerate + except Exception as exc: # noqa: BLE001 + logger.warning("ensure_json_exists: failed to read %s, regenerating: %s", json_path, exc) # Create from inline default factory = _DEFAULTS.get(json_type) diff --git a/src/aipass/drone/apps/handlers/module_registry_handler.py b/src/aipass/drone/apps/handlers/module_registry_handler.py index f1277172..12e601eb 100644 --- a/src/aipass/drone/apps/handlers/module_registry_handler.py +++ b/src/aipass/drone/apps/handlers/module_registry_handler.py @@ -65,7 +65,8 @@ def get_module_info(name: str) -> ModuleInfo | None: description=meta.get("description", ""), adapter_path=adapter_path, ) - except ImportError: + except ImportError as exc: + logger.warning("get_module_info: failed to import adapter '%s': %s", adapter_path, exc) return None @@ -93,7 +94,8 @@ def get_module_help(name: str, command: str | None = None) -> str: if help_fn is None: return "" return help_fn(command) - except (ImportError, AttributeError): + except (ImportError, AttributeError) as exc: + logger.warning("get_module_help: failed for module '%s': %s", name, exc) return "" @@ -115,7 +117,8 @@ def get_module_introspective(name: str) -> str: if help_fn is not None: return help_fn(None) return "" - except (ImportError, AttributeError): + except (ImportError, AttributeError) as exc: + logger.warning("get_module_introspective: failed for module '%s': %s", name, exc) return "" diff --git a/src/aipass/drone/apps/handlers/registry_handler.py b/src/aipass/drone/apps/handlers/registry_handler.py index a1a6b644..78070791 100644 --- a/src/aipass/drone/apps/handlers/registry_handler.py +++ b/src/aipass/drone/apps/handlers/registry_handler.py @@ -142,8 +142,8 @@ def _verify_registry_credential(registry_path: Path, registry_data: Dict[str, An ) except RegistryMismatchError: raise - except Exception: - pass # Verification should never crash drone + except Exception as exc: + logger.warning("Registry credential verification failed: %s", exc) def set_registry_path(path: str | Path) -> None: @@ -235,6 +235,7 @@ def get_all_branches( try: registry = load_registry() except RegistryNotFoundError: + logger.warning("get_all_branches: registry not found, returning empty list") return [] branches = registry.get("branches", {}).values() @@ -255,6 +256,7 @@ def get_branch_by_name(name: str) -> Optional[Dict[str, Any]]: try: registry = load_registry() except RegistryNotFoundError: + logger.warning("get_branch_by_name: registry not found for lookup of '%s'", name) return None return registry.get("branches", {}).get(name.lower()) diff --git a/src/aipass/drone/apps/handlers/router_handler.py b/src/aipass/drone/apps/handlers/router_handler.py index e4c3cc7d..9dbd5f82 100644 --- a/src/aipass/drone/apps/handlers/router_handler.py +++ b/src/aipass/drone/apps/handlers/router_handler.py @@ -59,6 +59,7 @@ def detect_caller_branch_name(cwd: Path) -> str | None: name = data.get("identity", {}).get("name") return name except Exception: + logger.warning("Failed to read passport at %s", passport) return None parent = current.parent if parent == current: diff --git a/src/aipass/drone/apps/handlers/scanning/formatters.py b/src/aipass/drone/apps/handlers/scanning/formatters.py index 8d39477b..a0ef57e5 100644 --- a/src/aipass/drone/apps/handlers/scanning/formatters.py +++ b/src/aipass/drone/apps/handlers/scanning/formatters.py @@ -17,6 +17,8 @@ from __future__ import annotations try: from aipass.cli.apps.modules import console except ImportError: + from aipass.prax import logger + logger.warning("formatters: aipass.cli.apps.modules.console unavailable, using fallback Rich Console") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/handlers/scanning/scanner.py b/src/aipass/drone/apps/handlers/scanning/scanner.py index 5e5ffa63..dbec1763 100644 --- a/src/aipass/drone/apps/handlers/scanning/scanner.py +++ b/src/aipass/drone/apps/handlers/scanning/scanner.py @@ -127,6 +127,7 @@ def scan_module_files(branch_path: str) -> list[dict]: try: source = py_file.read_text(encoding="utf-8") except OSError: + logger.warning("scan_module_files: could not read %s", py_file) continue if "def handle_command" not in source: diff --git a/src/aipass/drone/apps/modules/commands.py b/src/aipass/drone/apps/modules/commands.py index 31449c42..145965ed 100644 --- a/src/aipass/drone/apps/modules/commands.py +++ b/src/aipass/drone/apps/modules/commands.py @@ -141,6 +141,7 @@ def print_introspection() -> None: try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() @@ -160,6 +161,7 @@ def print_help() -> None: try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/config.py b/src/aipass/drone/apps/modules/config.py index 84ee8d95..9b4676c0 100644 --- a/src/aipass/drone/apps/modules/config.py +++ b/src/aipass/drone/apps/modules/config.py @@ -30,6 +30,7 @@ def print_introspection(): try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/discovery.py b/src/aipass/drone/apps/modules/discovery.py index 96f6dc8b..f8a2c692 100644 --- a/src/aipass/drone/apps/modules/discovery.py +++ b/src/aipass/drone/apps/modules/discovery.py @@ -83,6 +83,7 @@ def print_introspection(): try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/git_module.py b/src/aipass/drone/apps/modules/git_module.py index cb364c1f..dd051966 100644 --- a/src/aipass/drone/apps/modules/git_module.py +++ b/src/aipass/drone/apps/modules/git_module.py @@ -18,6 +18,7 @@ from __future__ import annotations import json from pathlib import Path +from aipass.prax import logger from aipass.drone.apps.handlers.json import json_handler from aipass.drone.apps.handlers.git import lock_handler, status_handler, sync_handler, pr_handler @@ -254,6 +255,7 @@ def print_introspection() -> None: try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() @@ -265,6 +267,7 @@ def print_help() -> None: try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/module_registry.py b/src/aipass/drone/apps/modules/module_registry.py index ed7a477b..72ad2c71 100644 --- a/src/aipass/drone/apps/modules/module_registry.py +++ b/src/aipass/drone/apps/modules/module_registry.py @@ -45,6 +45,7 @@ def print_introspection(): try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/registry.py b/src/aipass/drone/apps/modules/registry.py index 97cc969b..9a297a75 100644 --- a/src/aipass/drone/apps/modules/registry.py +++ b/src/aipass/drone/apps/modules/registry.py @@ -31,6 +31,7 @@ def print_introspection(): try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/resolver.py b/src/aipass/drone/apps/modules/resolver.py index 8ad9bf49..fe1955a2 100644 --- a/src/aipass/drone/apps/modules/resolver.py +++ b/src/aipass/drone/apps/modules/resolver.py @@ -89,6 +89,7 @@ def print_introspection(): try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/router.py b/src/aipass/drone/apps/modules/router.py index 684fdd7f..9c6b347b 100644 --- a/src/aipass/drone/apps/modules/router.py +++ b/src/aipass/drone/apps/modules/router.py @@ -117,6 +117,7 @@ def print_introspection(): try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() diff --git a/src/aipass/drone/apps/modules/scan.py b/src/aipass/drone/apps/modules/scan.py index 3b780b99..3fc23486 100644 --- a/src/aipass/drone/apps/modules/scan.py +++ b/src/aipass/drone/apps/modules/scan.py @@ -66,6 +66,7 @@ def print_introspection() -> None: try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() @@ -89,6 +90,7 @@ def print_help() -> None: try: from aipass.cli.apps.modules.display import console except ImportError: + logger.warning("CLI console not available, using fallback") from rich.console import Console console = Console() @@ -125,6 +127,7 @@ def scan(target: str) -> list[dict] | None: from aipass.cli.apps.modules import err_console err_console.print(f"scan: could not resolve '{target}': {exc}") except ImportError: + logger.warning("CLI err_console not available, skipping user-facing error") pass return None diff --git a/src/aipass/drone/drone_adapter.py b/src/aipass/drone/drone_adapter.py index 764ed1fc..7f3ea9c9 100644 --- a/src/aipass/drone/drone_adapter.py +++ b/src/aipass/drone/drone_adapter.py @@ -8,6 +8,8 @@ and routes `drone @drone [args]` here. import sys from io import StringIO +from aipass.prax import logger + DRONE_MODULE = { "name": "drone", "version": "1.0.0", @@ -89,4 +91,5 @@ def get_introspective() -> str: f" Run 'drone @drone --help' for usage\n" ) except Exception: + logger.warning("get_introspective: failed to load module list or branch list") return "@drone — Command routing and module discovery (run 'drone --help' for usage)\n" diff --git a/src/aipass/flow/.aipass/aipass_local_prompt.md b/src/aipass/flow/.aipass/aipass_local_prompt.md index 66be784b..75857ad2 100644 --- a/src/aipass/flow/.aipass/aipass_local_prompt.md +++ b/src/aipass/flow/.aipass/aipass_local_prompt.md @@ -10,7 +10,7 @@ drone @flow create . "Subject" master # FPLAN master template drone @flow create . "Design topic" dplan # DPLAN drone @flow close FPLAN-0042 # Close specific plan drone @flow close --all # Close all open plans -drone @flow list # List open plans (all types) +drone @flow list open # List open plans (all types) drone @flow list all # List all plans drone @flow restore FPLAN-0042 # Reopen a closed plan ``` diff --git a/src/aipass/flow/README.md b/src/aipass/flow/README.md index 88049c39..cf32b414 100644 --- a/src/aipass/flow/README.md +++ b/src/aipass/flow/README.md @@ -28,7 +28,7 @@ drone @flow create . "Design topic" dplan # Create DPLAN drone @flow close FPLAN-0042 # Close an FPLAN drone @flow close DPLAN-0005 # Close a DPLAN drone @flow close --all # Close all open plans -drone @flow list # List all plan types +drone @flow list open # List open plans drone @flow --help # Full help ``` diff --git a/src/aipass/flow/apps/flow.py b/src/aipass/flow/apps/flow.py index 0bbe0c98..6c75d5aa 100755 --- a/src/aipass/flow/apps/flow.py +++ b/src/aipass/flow/apps/flow.py @@ -301,8 +301,9 @@ if __name__ == "__main__": sys.exit(main()) except BrokenPipeError: import os + logger.info("[FLOW] Broken pipe in main (stdout closed early)") try: sys.stdout.close() except Exception as e: - print(f"Error: {e}") + logger.warning(f"[FLOW] Error closing stdout after broken pipe: {e}") os._exit(0) diff --git a/src/aipass/flow/apps/handlers/config/load_config.py b/src/aipass/flow/apps/handlers/config/load_config.py index ac30039a..b223851f 100644 --- a/src/aipass/flow/apps/handlers/config/load_config.py +++ b/src/aipass/flow/apps/handlers/config/load_config.py @@ -29,6 +29,7 @@ from pathlib import Path from datetime import datetime, timezone from typing import Dict, Any +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # INFRASTRUCTURE IMPORT PATTERN @@ -72,7 +73,8 @@ def create_default_config(config_file: Path, module_name: str, default_settings: with open(config_file, 'w', encoding='utf-8') as f: json.dump(default_config, f, indent=2, ensure_ascii=False) return default_config - except Exception: + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to create default config at {config_file}: {e}") return default_config @@ -110,5 +112,6 @@ def load_config(module_name: str, default_settings: Dict[str, Any] | None = None "success": True, }) return data - except Exception: + except Exception as e: + logger.error(f"[{MODULE_NAME}] Failed to load config for '{module_name}' from {config_file}: {e}") return {"config": default_settings or {"enabled": True}} diff --git a/src/aipass/flow/apps/handlers/dashboard/push_branch_dashboard.py b/src/aipass/flow/apps/handlers/dashboard/push_branch_dashboard.py index 16ea5333..c99d2134 100644 --- a/src/aipass/flow/apps/handlers/dashboard/push_branch_dashboard.py +++ b/src/aipass/flow/apps/handlers/dashboard/push_branch_dashboard.py @@ -47,6 +47,7 @@ from pathlib import Path from typing import Dict, Any, List, Tuple from aipass.flow.apps.handlers.json import json_handler +from aipass.prax.apps.modules.logger import system_logger as logger # INFRASTRUCTURE IMPORT PATTERN _PKG_ROOT = Path(__file__).resolve().parents[4] @@ -88,7 +89,8 @@ def _write_dashboard_section(branch_path: Path, section_name: str, section_data: if content: try: dashboard = json.loads(content) - except json.JSONDecodeError: + except json.JSONDecodeError as exc: + logger.warning("Corrupt dashboard JSON at '%s', creating fresh: %s", dashboard_path, exc) dashboard = _create_fresh_dashboard(branch_path) else: dashboard = _create_fresh_dashboard(branch_path) @@ -106,7 +108,8 @@ def _write_dashboard_section(branch_path: Path, section_name: str, section_data: dashboard_path.write_text(json.dumps(dashboard, indent=2)) return True - except Exception: + except Exception as exc: + logger.error("Failed to write dashboard section '%s' for branch '%s': %s", section_name, branch_path, exc) return False @@ -128,8 +131,8 @@ def _create_fresh_dashboard(branch_path: Path) -> Dict[str, Any]: ) dashboard["last_updated"] = datetime.now().isoformat() return dashboard - except (json.JSONDecodeError, OSError): - pass + except (json.JSONDecodeError, OSError) as exc: + logger.warning("Failed to load dashboard template '%s', using fallback: %s", DASHBOARD_TEMPLATE_FILE, exc) now = datetime.now().isoformat() return { @@ -204,7 +207,8 @@ def _load_registry() -> Dict[str, Any]: return {"plans": {}, "next_number": 1} with open(REGISTRY_FILE, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as exc: + logger.warning("Failed to load fplan registry '%s': %s", REGISTRY_FILE, exc) return {"plans": {}, "next_number": 1} @@ -260,8 +264,9 @@ def _filter_branch_plans( "subject": plan_data.get("subject", ""), "closed": closed_ts }) - except (ValueError, TypeError): + except (ValueError, TypeError) as exc: # If we can't parse the timestamp, include it anyway + logger.warning("Unparseable closed timestamp '%s' for plan %s, including anyway: %s", closed_ts, plan_id, exc) closed_plans.append({ "id": plan_id, "subject": plan_data.get("subject", ""), @@ -354,5 +359,6 @@ def push_flow_to_branch_dashboard(branch_path: Path) -> bool: return result - except Exception: + except Exception as exc: + logger.error("Failed to push flow section to branch dashboard '%s': %s", branch_path, exc) return False diff --git a/src/aipass/flow/apps/handlers/dashboard/push_central.py b/src/aipass/flow/apps/handlers/dashboard/push_central.py index c6ffe95d..26967480 100644 --- a/src/aipass/flow/apps/handlers/dashboard/push_central.py +++ b/src/aipass/flow/apps/handlers/dashboard/push_central.py @@ -32,6 +32,7 @@ from datetime import datetime, timezone from typing import Dict, Any, List from aipass.flow.apps.handlers.json import json_handler +from aipass.prax.apps.modules.logger import system_logger as logger # INFRASTRUCTURE IMPORT PATTERN _PKG_ROOT = Path(__file__).resolve().parents[4] @@ -76,7 +77,8 @@ def _load_registry() -> Dict[str, Any]: try: with open(REGISTRY_FILE, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as exc: + logger.warning("Failed to load fplan registry '%s': %s", REGISTRY_FILE, exc) return {"plans": {}, "next_number": 1} @@ -147,7 +149,8 @@ def _load_central() -> Dict[str, Any]: try: with open(CENTRAL_FILE, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as exc: + logger.warning("Failed to load PLANS.central.json '%s': %s", CENTRAL_FILE, exc) return { "generated_at": "", "branches": {}, @@ -262,5 +265,6 @@ def push_to_plans_central() -> bool: return True - except Exception: + except Exception as exc: + logger.error("Failed to push plans to central '%s': %s", CENTRAL_FILE, exc) return False diff --git a/src/aipass/flow/apps/handlers/dashboard/update_local.py b/src/aipass/flow/apps/handlers/dashboard/update_local.py index 0ced529a..62fc63e1 100644 --- a/src/aipass/flow/apps/handlers/dashboard/update_local.py +++ b/src/aipass/flow/apps/handlers/dashboard/update_local.py @@ -74,6 +74,7 @@ from datetime import datetime, timezone from typing import Dict, Any, List, Optional from aipass.flow.apps.handlers.json import json_handler +from aipass.prax.apps.modules.logger import system_logger as logger # INFRASTRUCTURE IMPORT PATTERN _PKG_ROOT = Path(__file__).resolve().parents[4] @@ -102,7 +103,8 @@ def _read_registry() -> Optional[Dict[str, Any]]: return None with open(REGISTRY_FILE, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as exc: + logger.warning("Failed to read fplan registry '%s': %s", REGISTRY_FILE, exc) return None @@ -193,7 +195,8 @@ def _read_existing_dashboard() -> Dict[str, Any]: return {} # Parse the JSON content we just read return json.loads(content) - except Exception: + except Exception as exc: + logger.warning("Failed to read existing dashboard '%s': %s", DASHBOARD_FILE, exc) return {} @@ -242,7 +245,8 @@ def _write_dashboard(dashboard: Dict[str, Any]) -> bool: with open(DASHBOARD_FILE, 'w', encoding='utf-8') as f: json.dump(dashboard, f, indent=2, ensure_ascii=False) return True - except Exception: + except Exception as exc: + logger.error("Failed to write dashboard '%s': %s", DASHBOARD_FILE, exc) return False diff --git a/src/aipass/flow/apps/handlers/json/json_handler.py b/src/aipass/flow/apps/handlers/json/json_handler.py index 0cb5a128..b0006961 100644 --- a/src/aipass/flow/apps/handlers/json/json_handler.py +++ b/src/aipass/flow/apps/handlers/json/json_handler.py @@ -19,6 +19,8 @@ from datetime import datetime from typing import Dict, Any, Optional import inspect +from aipass.prax.apps.modules.logger import system_logger as logger + # Infrastructure _PKG_ROOT = Path(__file__).resolve().parents[4] @@ -49,7 +51,8 @@ def _get_caller_module_name() -> str: # Fallback return "unknown" - except Exception: + except Exception as exc: + logger.warning("[json_handler] Failed to detect caller module name: %s", exc) return "unknown" @@ -70,7 +73,8 @@ def load_template(json_type: str, module_name: str) -> Any: template_str = template_str.replace("{{TIMESTAMP}}", datetime.now().date().isoformat()) return json.loads(template_str) - except Exception: + except Exception as exc: + logger.warning("[json_handler] Failed to load template '%s' for module '%s': %s", json_type, module_name, exc) return None @@ -113,9 +117,9 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: if validate_json_structure(data, json_type): return True - except Exception: + except Exception as exc: # File exists but is corrupted - will regenerate below - pass + logger.warning("[json_handler] Corrupted JSON file for '%s/%s', regenerating: %s", module_name, json_type, exc) template = load_template(json_type, module_name) if template is None: @@ -125,7 +129,8 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: with open(json_path, 'w', encoding='utf-8') as f: json.dump(template, f, indent=2, ensure_ascii=False) return True - except Exception: + except Exception as exc: + logger.error("[json_handler] Failed to write JSON template for '%s/%s': %s", module_name, json_type, exc) return False @@ -139,7 +144,8 @@ def load_json(module_name: str, json_type: str) -> Optional[Any]: try: with open(json_path, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as exc: + logger.error("[json_handler] Failed to load JSON for '%s/%s': %s", module_name, json_type, exc) return None @@ -157,7 +163,8 @@ def save_json(module_name: str, json_type: str, data: Any) -> bool: with open(json_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return True - except Exception: + except Exception as exc: + logger.error("[json_handler] Failed to save JSON for '%s/%s': %s", module_name, json_type, exc) return False diff --git a/src/aipass/flow/apps/handlers/mbank/process.py b/src/aipass/flow/apps/handlers/mbank/process.py index 7927dc62..bf33bdd0 100644 --- a/src/aipass/flow/apps/handlers/mbank/process.py +++ b/src/aipass/flow/apps/handlers/mbank/process.py @@ -73,8 +73,8 @@ def _is_branch_private(branch_name: str) -> bool: for branch in registry.get("branches", []): if branch.get("name", "").upper() == branch_name.upper(): return True - except (json.JSONDecodeError, IOError): - pass + except (json.JSONDecodeError, IOError) as exc: + logger.warning("[mbank] Failed to read private branch registry for '%s': %s", branch_name, exc) return False @@ -88,8 +88,8 @@ def _get_private_branch_path(branch_name: str) -> Optional[str]: for branch in registry.get("branches", []): if branch.get("name", "").upper() == branch_name.upper(): return branch.get("path") - except (json.JSONDecodeError, IOError): - pass + except (json.JSONDecodeError, IOError) as exc: + logger.warning("[mbank] Failed to read private branch path for '%s': %s", branch_name, exc) return None @@ -112,8 +112,8 @@ def _get_private_branch_for_path(plan_path: Path) -> Optional[Dict[str, str]]: branch_path = branch.get("path", "") if branch_path and plan_str.startswith(branch_path): return {"name": branch.get("name", ""), "path": branch_path} - except (json.JSONDecodeError, IOError): - pass + except (json.JSONDecodeError, IOError) as exc: + logger.warning("[mbank] Failed to check private branch for path '%s': %s", plan_path, exc) return None @@ -218,7 +218,8 @@ def get_ai_model() -> Optional[str]: return None - except Exception: + except Exception as exc: + logger.warning("[mbank] Failed to read AI model from API config: %s", exc) return None # ============================================= @@ -237,8 +238,8 @@ def _get_all_registry_files() -> List[str]: files.append(rf) if files: return files - except Exception: - pass + except Exception as exc: + logger.warning("[mbank] Failed to discover plan types, falling back to default registry: %s", exc) return [REGISTRY_FILE.name] @@ -278,7 +279,8 @@ def get_closed_plans() -> List[Dict[str, Any]]: for reg_file in _get_all_registry_files(): try: registry = load_flow_registry(registry_file=reg_file) - except Exception: + except Exception as exc: + logger.warning("[mbank] Failed to load registry '%s' while fetching closed plans: %s", reg_file, exc) continue for plan_num, plan_info in registry.get("plans", {}).items(): if plan_info.get("status") == "closed" and plan_info.get("processed") is not True: @@ -599,7 +601,8 @@ def archive_plan(plan_path: Path) -> bool: return True - except Exception: + except Exception as exc: + logger.error("[mbank] Failed to archive plan '%s': %s", plan_path, exc) return False # ============================================= @@ -688,7 +691,8 @@ def verify_and_heal_orphaned_plans() -> Dict[str, Any]: for reg_file in _get_all_registry_files(): try: registry = load_flow_registry(registry_file=reg_file) - except Exception: + except Exception as exc: + logger.warning("[mbank] Failed to load registry '%s' during orphan healing: %s", reg_file, exc) continue for plan_num, plan_info in registry.get("plans", {}).items(): # Heal ANY closed plan whose file still sits at its original location. diff --git a/src/aipass/flow/apps/handlers/plan/aggregate_ops.py b/src/aipass/flow/apps/handlers/plan/aggregate_ops.py index fdb5a6e8..6f2a1617 100644 --- a/src/aipass/flow/apps/handlers/plan/aggregate_ops.py +++ b/src/aipass/flow/apps/handlers/plan/aggregate_ops.py @@ -417,8 +417,8 @@ def aggregate_central_impl(heal: bool = True, active_count=len(all_active), closed_count=len(recently_closed), branches_count=len(branches)) - except ImportError: - pass + except ImportError as e: + logger.warning(f"[{MODULE_NAME}] Trigger module not available, skipping central_aggregated event: {e}") json_handler.log_operation("central_aggregated", {"active_count": len(all_active), "closed_count": len(recently_closed), "success": True}) return True diff --git a/src/aipass/flow/apps/handlers/plan/calculate_relative_path.py b/src/aipass/flow/apps/handlers/plan/calculate_relative_path.py index babd35cb..796e1e14 100644 --- a/src/aipass/flow/apps/handlers/plan/calculate_relative_path.py +++ b/src/aipass/flow/apps/handlers/plan/calculate_relative_path.py @@ -14,6 +14,7 @@ Calculates relative paths from ecosystem root for plan location display. from pathlib import Path +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler @@ -68,4 +69,5 @@ def calculate_relative_location( except ValueError: # target_dir is outside ecosystem_root + logger.warning(f"[calculate_relative_path] Target '{target_dir}' is outside ecosystem root '{ecosystem_root}', using absolute path") return str(target_dir) diff --git a/src/aipass/flow/apps/handlers/plan/close_ops.py b/src/aipass/flow/apps/handlers/plan/close_ops.py index 20dcfee4..a77bc675 100644 --- a/src/aipass/flow/apps/handlers/plan/close_ops.py +++ b/src/aipass/flow/apps/handlers/plan/close_ops.py @@ -61,7 +61,8 @@ def _resolve_registry_file(plan_num_raw: str) -> str | None: from aipass.flow.apps.handlers.template.plan_type_loader import get_plan_type # type: ignore[import-not-found] config = get_plan_type(prefix) return config.get("registry_file") - except Exception: + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to resolve registry file for prefix '{prefix}': {e}") return None @@ -80,10 +81,11 @@ def _find_plan_across_registries(plan_key: str, load_registry_fn: Any) -> str | registry = load_registry_fn(registry_file=reg_file) if plan_key in registry.get("plans", {}): return reg_file - except Exception: + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to search registry '{reg_file}' for plan '{plan_key}': {e}") continue - except Exception: - pass + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to discover plan types while searching for plan '{plan_key}': {e}") return None @@ -351,8 +353,8 @@ def close_plan_impl(plan_num: Any = None, confirm: bool = False, ["drone", "@memory", "process-plans"], capture_output=True, timeout=30, ) - except Exception: - pass # Best effort — verification below reports actual status + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Best-effort drone @memory process-plans failed: {e}") # Verify vectorization via memory's verify module try: diff --git a/src/aipass/flow/apps/handlers/plan/command_parser.py b/src/aipass/flow/apps/handlers/plan/command_parser.py index 8d499c40..1271a68d 100644 --- a/src/aipass/flow/apps/handlers/plan/command_parser.py +++ b/src/aipass/flow/apps/handlers/plan/command_parser.py @@ -14,8 +14,11 @@ Parses command-line arguments for plan operations. from typing import List, Tuple +from aipass.prax import logger from aipass.flow.apps.handlers.json import json_handler +MODULE_NAME = "command_parser" + def parse_create_plan_args(args: List[str]) -> Tuple[str | None, str, str]: """ @@ -59,7 +62,8 @@ def parse_create_plan_args(args: List[str]) -> Tuple[str | None, str, str]: try: from aipass.flow.apps.handlers.template.registry_ops import get_type_map type_map = get_type_map() - except Exception: + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to load type map from registry_ops, using defaults: {e}") type_map = {"default": "flow_plans", "dplan": "dev_plans"} plan_type_key = type_map.get(raw_type.lower(), raw_type) diff --git a/src/aipass/flow/apps/handlers/plan/confirmation.py b/src/aipass/flow/apps/handlers/plan/confirmation.py index 0a4488a6..af1e4277 100644 --- a/src/aipass/flow/apps/handlers/plan/confirmation.py +++ b/src/aipass/flow/apps/handlers/plan/confirmation.py @@ -15,6 +15,7 @@ User interaction and confirmation prompts for plan operations. import sys from pathlib import Path +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # Infrastructure @@ -53,4 +54,5 @@ def confirm_plan_deletion(plan_key: str) -> bool: return response in ['yes', 'y'] except EOFError: # Fallback for edge cases where isatty() returns True but input fails + logger.warning(f"[confirmation] EOFError reading input for plan {plan_key} deletion, auto-confirming") return True diff --git a/src/aipass/flow/apps/handlers/plan/create.py b/src/aipass/flow/apps/handlers/plan/create.py index ddeebac0..ef3c6672 100644 --- a/src/aipass/flow/apps/handlers/plan/create.py +++ b/src/aipass/flow/apps/handlers/plan/create.py @@ -38,6 +38,7 @@ from pathlib import Path from datetime import datetime, timezone from typing import Dict, Any, Tuple +from aipass.prax import logger from aipass.flow.apps.handlers.json import json_handler _PKG_ROOT = Path(__file__).resolve().parents[4] FLOW_ROOT = _PKG_ROOT / "flow" @@ -86,6 +87,7 @@ def write_plan_file(plan_file: Path, content: str) -> Tuple[bool, str]: except Exception as e: error_msg = f"Failed to write plan file: {e}" + logger.error(f"[{MODULE_NAME}] {error_msg}") return False, error_msg @@ -114,7 +116,8 @@ def create_registry_entry( RELATIVE_LOCATION = str(target_dir.relative_to(ECOSYSTEM_ROOT)) if RELATIVE_LOCATION == ".": RELATIVE_LOCATION = "root" - except ValueError: + except ValueError as e: + logger.warning(f"[{MODULE_NAME}] Could not compute relative location for '{target_dir}': {e}") RELATIVE_LOCATION = str(target_dir) # Build plan file path diff --git a/src/aipass/flow/apps/handlers/plan/create_file.py b/src/aipass/flow/apps/handlers/plan/create_file.py index 3667f5e0..2c635187 100644 --- a/src/aipass/flow/apps/handlers/plan/create_file.py +++ b/src/aipass/flow/apps/handlers/plan/create_file.py @@ -15,6 +15,7 @@ Creates plan files with validation and error handling. from pathlib import Path from typing import Tuple +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler diff --git a/src/aipass/flow/apps/handlers/plan/create_ops.py b/src/aipass/flow/apps/handlers/plan/create_ops.py index a1d4fce7..4297c52f 100644 --- a/src/aipass/flow/apps/handlers/plan/create_ops.py +++ b/src/aipass/flow/apps/handlers/plan/create_ops.py @@ -261,7 +261,7 @@ def create_plan_impl( from aipass.trigger.apps.modules.core import trigger trigger.fire('plan_created', plan_number=NEXT_NUM, location=RELATIVE_LOCATION, subject=subject) except ImportError: - pass # Trigger not available, silent fallback + logger.info(f"[{MODULE_NAME}] Trigger module not available, skipping plan_created event") json_handler.log_operation("plan_created", {"plan_number": NEXT_NUM, "location": RELATIVE_LOCATION, "template": template_type, "success": True}) return True, NEXT_NUM, RELATIVE_LOCATION, template_type, "", messages diff --git a/src/aipass/flow/apps/handlers/plan/display.py b/src/aipass/flow/apps/handlers/plan/display.py index 2f1a5031..10d82c0e 100644 --- a/src/aipass/flow/apps/handlers/plan/display.py +++ b/src/aipass/flow/apps/handlers/plan/display.py @@ -16,6 +16,7 @@ Returns formatted strings - caller handles actual output. from pathlib import Path from typing import Dict, Any +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler @@ -325,8 +326,8 @@ def format_plan_info(plan_key: str, plan_info: Dict[str, Any], prefix: str = "FP try: dt = datetime.fromisoformat(created.replace('Z', '+00:00')) created = dt.strftime("%Y-%m-%d %H:%M") - except (ValueError, AttributeError): - pass # Keep original value if parsing fails + except (ValueError, AttributeError) as e: + logger.warning(f"[display] Failed to parse created date '{created}': {e}") # Use original plan number if available (set by list_ops merge), else use key display_num = plan_info.get("_plan_num", plan_key) diff --git a/src/aipass/flow/apps/handlers/plan/get_open_plans.py b/src/aipass/flow/apps/handlers/plan/get_open_plans.py index 5c0e9b8b..f4dc77d4 100644 --- a/src/aipass/flow/apps/handlers/plan/get_open_plans.py +++ b/src/aipass/flow/apps/handlers/plan/get_open_plans.py @@ -19,9 +19,13 @@ Usage: from pathlib import Path from typing import List, Tuple, Dict, Any +from aipass.prax import logger + # INFRASTRUCTURE IMPORT PATTERN _PKG_ROOT = Path(__file__).resolve().parents[4] +MODULE_NAME = "get_open_plans" + # Internal: Registry handler from aipass.flow.apps.handlers.registry.load_registry import load_registry from aipass.flow.apps.handlers.json import json_handler @@ -41,8 +45,8 @@ def _get_all_registry_files() -> List[str]: files.append(rf) if files: return files - except Exception: - pass + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to discover plan types for registry files: {e}") return [] @@ -66,7 +70,8 @@ def get_open_plans() -> List[Tuple[str, Dict[str, Any]]]: for plan_num, plan_info in registry.get("plans", {}).items() if plan_info.get("status") == "open" ) - except Exception: + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to load registry '{reg_file}' for open plan scan: {e}") continue else: # Fallback: load default registry diff --git a/src/aipass/flow/apps/handlers/plan/list_ops.py b/src/aipass/flow/apps/handlers/plan/list_ops.py index 04b072ba..754eeccf 100644 --- a/src/aipass/flow/apps/handlers/plan/list_ops.py +++ b/src/aipass/flow/apps/handlers/plan/list_ops.py @@ -48,8 +48,8 @@ def _get_all_registry_info() -> Tuple[list[str], Dict[str, str]]: files.append(rf) if files: return files, prefix_map - except Exception: - pass + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to discover plan types for registry info: {e}") return [], {} # empty means caller should fall back to default @@ -96,7 +96,8 @@ def list_plans_impl( # Use prefix-qualified key to avoid collisions across registries merge_key = f"{source_prefix}-{plan_num}" merged_plans[merge_key] = plan_info - except Exception: + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Failed to load registry '{reg_file}' for plan listing: {e}") continue else: # Fallback: load default registry diff --git a/src/aipass/flow/apps/handlers/plan/restore_ops.py b/src/aipass/flow/apps/handlers/plan/restore_ops.py index 45cc4bca..09e99bda 100644 --- a/src/aipass/flow/apps/handlers/plan/restore_ops.py +++ b/src/aipass/flow/apps/handlers/plan/restore_ops.py @@ -110,11 +110,12 @@ def recover_plan_from_backup(plan_key: str, load_registry: Any = None, save_regi else: try: relative_path = str(original_path.relative_to(_PKG_ROOT)) - except ValueError: + except ValueError as e: + logger.warning(f"[{MODULE_NAME}] Could not compute relative path for '{original_path}': {e}") relative_path = str(original_path) except Exception as e: - # If parsing fails, default to FLOW_ROOT + logger.warning(f"[{MODULE_NAME}] Failed to parse plan file '{plan_file}' for recovery, defaulting to FLOW_ROOT: {e}") original_location = str(FLOW_ROOT) relative_path = "flow" diff --git a/src/aipass/flow/apps/handlers/registry/load_registry.py b/src/aipass/flow/apps/handlers/registry/load_registry.py index 16e224d3..26948e1a 100644 --- a/src/aipass/flow/apps/handlers/registry/load_registry.py +++ b/src/aipass/flow/apps/handlers/registry/load_registry.py @@ -26,6 +26,7 @@ import json from pathlib import Path from typing import Dict, Any +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # INFRASTRUCTURE IMPORT PATTERN @@ -74,5 +75,6 @@ def load_registry(registry_file: str | None = None) -> Dict[str, Any]: "success": True, }) return data - except Exception: + except Exception as e: + logger.error(f"[{MODULE_NAME}] Failed to load registry from {target}: {e}") return {"plans": {}, "next_number": 1} diff --git a/src/aipass/flow/apps/handlers/registry/save_registry.py b/src/aipass/flow/apps/handlers/registry/save_registry.py index 9110bafe..129a31e6 100644 --- a/src/aipass/flow/apps/handlers/registry/save_registry.py +++ b/src/aipass/flow/apps/handlers/registry/save_registry.py @@ -29,6 +29,7 @@ from pathlib import Path from datetime import datetime, timezone from typing import Dict, Any +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # INFRASTRUCTURE IMPORT PATTERN @@ -76,5 +77,6 @@ def save_registry(registry: Dict[str, Any], registry_file: str | None = None) -> "success": True, }) return True - except Exception: + except Exception as e: + logger.error(f"[{MODULE_NAME}] Failed to save registry to {target}: {e}") return False diff --git a/src/aipass/flow/apps/handlers/summary/write_plan_outputs.py b/src/aipass/flow/apps/handlers/summary/write_plan_outputs.py index 26932d18..7b95b6c7 100644 --- a/src/aipass/flow/apps/handlers/summary/write_plan_outputs.py +++ b/src/aipass/flow/apps/handlers/summary/write_plan_outputs.py @@ -39,6 +39,7 @@ from datetime import datetime, timezone from typing import Dict, Any, Optional from aipass.flow.apps.handlers.json import json_handler +from aipass.prax.apps.modules.logger import system_logger as logger # INFRASTRUCTURE IMPORT PATTERN _PKG_ROOT = Path(__file__).resolve().parents[4] @@ -98,7 +99,8 @@ def _normalize_plan_entry(plan_num: str, info: Dict[str, Any]) -> Optional[Dict[ try: branch_relative_path = str(path_obj.relative_to(_PKG_ROOT)) - except Exception: + except Exception as exc: + logger.warning("[write_plan_outputs] Could not resolve relative path for plan %s: %s", plan_num, exc) branch_relative_path = str(path_obj) else: branch_relative_path = file_path @@ -111,7 +113,8 @@ def _normalize_plan_entry(plan_num: str, info: Dict[str, Any]) -> Optional[Dict[ if branch_dir is not None and not branch_name: try: branch_name = branch_dir.relative_to(_PKG_ROOT).parts[0] - except Exception: + except Exception as exc: + logger.warning("[write_plan_outputs] Could not determine branch name from dir for plan %s: %s", plan_num, exc) branch_name = branch_dir.name if branch_dir.name else "unknown" entry = { @@ -132,14 +135,16 @@ def _normalize_plan_entry(plan_num: str, info: Dict[str, Any]) -> Optional[Dict[ if path_obj is not None and branch_dir is not None: try: entry["branch_relative_path"] = str(path_obj.relative_to(branch_dir)) - except Exception: + except Exception as exc: + logger.warning("[write_plan_outputs] Could not compute branch-relative path for plan %s: %s", plan_num, exc) entry["branch_relative_path"] = entry["relative_path"] if path_obj is not None: entry["absolute_path"] = str(path_obj) try: entry["file_uri"] = path_obj.as_uri() - except ValueError: + except ValueError as exc: + logger.warning("[write_plan_outputs] Could not generate file URI for plan %s: %s", plan_num, exc) entry["file_uri"] = None entry["vscode_uri"] = f"vscode://file{entry['absolute_path']}" if entry.get("absolute_path") else None @@ -148,7 +153,8 @@ def _normalize_plan_entry(plan_num: str, info: Dict[str, Any]) -> Optional[Dict[ try: branch_dir.relative_to(_PKG_ROOT) entry["branch_path"] = branch_dir - except Exception: + except Exception as exc: + logger.warning("[write_plan_outputs] Branch dir outside package root for plan %s: %s", plan_num, exc) entry["branch_path"] = None return entry @@ -221,7 +227,8 @@ def _write_central_summary_json(active_entries: list, closed_entries: list) -> b with open(CLAUDE_JSON_FILE, 'w', encoding='utf-8') as f: json.dump(payload, f, indent=2, ensure_ascii=False) return True - except Exception: + except Exception as exc: + logger.error("[write_plan_outputs] Failed to write central summary JSON: %s", exc) return False @@ -300,7 +307,8 @@ def _write_branch_local_files(branch_map: Dict[Path, Dict[str, Any]]) -> bool: branch_path.mkdir(parents=True, exist_ok=True) with open(file_path, 'w', encoding='utf-8') as f: f.write(content) - except Exception: + except Exception as exc: + logger.error("[write_plan_outputs] Failed to write branch-local file for '%s': %s", branch_name, exc) all_success = False return all_success diff --git a/src/aipass/flow/apps/handlers/template/list_templates.py b/src/aipass/flow/apps/handlers/template/list_templates.py index bc99bf27..bff7f4ea 100644 --- a/src/aipass/flow/apps/handlers/template/list_templates.py +++ b/src/aipass/flow/apps/handlers/template/list_templates.py @@ -26,6 +26,7 @@ Usage: from pathlib import Path +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.flow.apps.handlers.json import json_handler # INFRASTRUCTURE IMPORT PATTERN @@ -82,5 +83,6 @@ def list_templates() -> list[str]: return sorted_templates - except Exception: + except Exception as e: + logger.error(f"[{MODULE_NAME}] Failed to list templates: {e}") return [] diff --git a/src/aipass/flow/apps/modules/list_plans.py b/src/aipass/flow/apps/modules/list_plans.py index ff8b29ab..fda1d47a 100755 --- a/src/aipass/flow/apps/modules/list_plans.py +++ b/src/aipass/flow/apps/modules/list_plans.py @@ -146,7 +146,7 @@ def list_plans(filter_type: str = "open") -> bool: try: error(f"ERROR: {error_msg}") except BrokenPipeError: - pass + logger.info(f"[{MODULE_NAME}] Broken pipe while displaying error (stdout closed early)") return False # Display formatted results @@ -256,8 +256,9 @@ if __name__ == "__main__": except BrokenPipeError: # Pipe closed by reader - exit cleanly import os + logger.info(f"[{MODULE_NAME}] Broken pipe in standalone mode (stdout closed early)") try: sys.stdout.close() - except Exception: - pass + except Exception as e: + logger.warning(f"[{MODULE_NAME}] Error closing stdout after broken pipe: {e}") os._exit(0) diff --git a/src/aipass/flow/templates/flow_plans/default.md b/src/aipass/flow/templates/flow_plans/default.md index 7b81ad41..9dff9b5f 100644 --- a/src/aipass/flow/templates/flow_plans/default.md +++ b/src/aipass/flow/templates/flow_plans/default.md @@ -77,7 +77,7 @@ Don't figure everything out alone. Other branches are domain experts - ask them **Before building anything that touches another branch's domain:** ```bash -ai_mail send @branch "Question: [topic]" "I'm working on X and need guidance on Y. What's the best approach?" +ai_mail email @branch "Question: [topic]" "I'm working on X and need guidance on Y. What's the best approach?" ``` **Common examples:** @@ -111,7 +111,7 @@ When unsure about syntax, use `--help`: # Flow - Plan management drone @flow create . "subject" # Create plan (. = current dir) drone @flow close {prefix}-XXXX # Close plan -drone @flow list # List active plans +drone @flow list open # List active plans drone @flow --help # Full help # Seedgo - Quality gates @@ -120,7 +120,7 @@ drone @seedgo audit @branch # Full branch audit drone @seedgo --help # Full help # AI_Mail - Status updates -drone @ai_mail send @devpulse "Subject" "Message" +drone @ai_mail email @devpulse "Subject" "Message" drone @ai_mail --help # Full help # Discovery @@ -213,7 +213,7 @@ WHEN COMPLETE: **If production stops (critical blocker):** ```bash -drone @ai_mail send @devpulse "PRODUCTION STOPPED: {plan_number}" "Issue: [description]. Attempted: [what was tried]. Awaiting guidance." +drone @ai_mail email @devpulse "PRODUCTION STOPPED: {plan_number}" "Issue: [description]. Attempted: [what was tried]. Awaiting guidance." ``` --- @@ -237,7 +237,7 @@ drone @ai_mail send @devpulse "PRODUCTION STOPPED: {plan_number}" "Issue: [descr - [ ] README.md updated (if build changed status/capabilities) - [ ] Status email sent to @devpulse: ```bash - drone @ai_mail send @devpulse "{plan_number} Complete" "Summary of what was done, any issues, outcomes" + drone @ai_mail email @devpulse "{plan_number} Complete" "Summary of what was done, any issues, outcomes" ``` **Completion Order:** Memories -> README -> Email (README before email - don't report complete with stale docs) diff --git a/src/aipass/flow/templates/flow_plans/master.md b/src/aipass/flow/templates/flow_plans/master.md index f70e2b30..06dffafb 100644 --- a/src/aipass/flow/templates/flow_plans/master.md +++ b/src/aipass/flow/templates/flow_plans/master.md @@ -80,7 +80,7 @@ Don't figure everything out alone. Other branches are domain experts - ask them **Before building anything that touches another branch's domain:** ```bash -ai_mail send @branch "Question: [topic]" "I'm working on X and need guidance on Y. What's the best approach?" +ai_mail email @branch "Question: [topic]" "I'm working on X and need guidance on Y. What's the best approach?" ``` **Common examples:** @@ -120,7 +120,7 @@ When unsure about syntax, use `--help`: drone @flow create . "Phase X: subject" # Create sub-plan (. = current dir) drone @flow create . "subject" master # Create master plan drone @flow close {prefix}-XXXX # Close plan -drone @flow list # List active plans +drone @flow list open # List active plans drone @flow status # Plan status drone @flow --help # Full help @@ -130,7 +130,7 @@ drone @seedgo audit @branch # Full branch audit (before master clos drone @seedgo --help # Full help # AI_Mail - Status updates -drone @ai_mail send @devpulse "Subject" "Message" +drone @ai_mail email @devpulse "Subject" "Message" drone @ai_mail inbox # Check your inbox drone @ai_mail --help # Full help @@ -311,7 +311,7 @@ Seedgo audits are helpful but not infallible. If something causes production to STOP (critical blocker), **immediately email @devpulse**: ```bash -drone @ai_mail send @devpulse "PRODUCTION STOPPED: {plan_number}" "Phase X halted. Issue: [description]. Attempted: [what was tried]. Awaiting guidance." +drone @ai_mail email @devpulse "PRODUCTION STOPPED: {plan_number}" "Phase X halted. Issue: [description]. Attempted: [what was tried]. Awaiting guidance." ``` **Never leave a branch stopped without reporting.** The orchestration hub needs visibility into all work. @@ -496,7 +496,7 @@ Track issues here as you encounter them. Don't fix during build - log and contin - [ ] Artifacts reviewed (devpulse manages cleanup) - [ ] Final email to @devpulse: ```bash - drone @ai_mail send @devpulse "{plan_number} MASTER COMPLETE" "Full build summary: phases completed, deliverables, remaining issues (if any)" + drone @ai_mail email @devpulse "{plan_number} MASTER COMPLETE" "Full build summary: phases completed, deliverables, remaining issues (if any)" ``` **Completion Order:** Memories -> README -> Email (README before email - don't report complete with stale docs) diff --git a/src/aipass/prax/apps/handlers/central/reader.py b/src/aipass/prax/apps/handlers/central/reader.py index 1a060d22..c7769612 100644 --- a/src/aipass/prax/apps/handlers/central/reader.py +++ b/src/aipass/prax/apps/handlers/central/reader.py @@ -16,12 +16,15 @@ Used by dashboard/refresh.py to populate branch dashboards. """ import json +import logging from pathlib import Path from typing import Dict from aipass.prax.apps.handlers.config.load import _find_repo_root from aipass.prax.apps.handlers.json import json_handler +logger = logging.getLogger(__name__) + def read_all_centrals() -> Dict: """ @@ -45,7 +48,8 @@ def read_all_centrals() -> Dict: # Key by service name: AI_MAIL.central.json -> ai_mail service_name = central_file.name.replace(".central.json", "").lower() centrals[service_name] = data - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning("reader: failed to read central file '%s': %s", central_file.name, e) continue json_handler.log_operation("central_data_read", {"services_found": len(centrals)}) diff --git a/src/aipass/prax/apps/handlers/config/ignore_patterns.py b/src/aipass/prax/apps/handlers/config/ignore_patterns.py index 3a8008c5..a4e64090 100755 --- a/src/aipass/prax/apps/handlers/config/ignore_patterns.py +++ b/src/aipass/prax/apps/handlers/config/ignore_patterns.py @@ -27,12 +27,15 @@ Usage: """ import json +import logging from pathlib import Path from typing import Set from aipass.prax.apps.handlers.config.load import PRAX_ROOT from aipass.prax.apps.handlers.json import json_handler +logger = logging.getLogger(__name__) + # ============================================= # CONFIGURATION # ============================================= @@ -76,9 +79,8 @@ def load_ignore_patterns_from_config() -> Set[str]: if patterns: json_handler.log_operation("ignore_patterns_loaded", {"pattern_count": len(patterns)}) return set(patterns) - except Exception: - # Silently fall back to defaults - logging not available at this level - pass + except Exception as e: + logger.warning("ignore_patterns: failed to load config from '%s', using defaults: %s", PRAX_LOGGER_CONFIG_FILE, e) # Fallback to hardcoded if config missing/invalid return DEFAULT_IGNORE_FOLDERS diff --git a/src/aipass/prax/apps/handlers/config/load.py b/src/aipass/prax/apps/handlers/config/load.py index 80a5477f..9dc73e82 100755 --- a/src/aipass/prax/apps/handlers/config/load.py +++ b/src/aipass/prax/apps/handlers/config/load.py @@ -29,6 +29,7 @@ Usage: import json import logging +logger = logging.getLogger(__name__) from pathlib import Path from typing import Dict, Any @@ -154,7 +155,7 @@ def get_debug_prints_enabled() -> bool: config = json.load(f) return config.get('config', {}).get('debug_prints_enabled', False) except (json.JSONDecodeError, OSError) as e: - logging.debug(f"Config load error (using defaults): {e}") + logger.info(f"Config load error (using defaults): {e}") return False def load_log_config() -> Dict[str, Any]: @@ -202,7 +203,7 @@ def load_log_config() -> Dict[str, Any]: json_handler.log_operation("config_loaded", {"source": str(PRAX_LOGGER_CONFIG_FILE)}) return result except (json.JSONDecodeError, OSError) as e: - logging.debug(f"Log config load error (using defaults): {e}") + logger.info(f"Log config load error (using defaults): {e}") # Fallback to code defaults return { diff --git a/src/aipass/prax/apps/handlers/dashboard/agent_status_writer.py b/src/aipass/prax/apps/handlers/dashboard/agent_status_writer.py index 6583288d..c5394cdd 100644 --- a/src/aipass/prax/apps/handlers/dashboard/agent_status_writer.py +++ b/src/aipass/prax/apps/handlers/dashboard/agent_status_writer.py @@ -20,12 +20,15 @@ Data sources: """ import json +import logging import subprocess import sys from datetime import datetime from pathlib import Path from typing import Any, Dict, List +logger = logging.getLogger(__name__) + from aipass.prax.apps.handlers.json import json_handler @@ -74,7 +77,8 @@ def _get_all_branches() -> List[Dict[str, Any]]: "path": branch_path }) return branches - except Exception: + except Exception as e: + logger.warning("Failed to load branch registry: %s", e) return [] @@ -96,7 +100,8 @@ def _is_pid_alive(pid: int) -> bool: return False cmdline = cmdline_path.read_bytes().decode("utf-8", errors="replace") return "claude" in cmdline.lower() - except (PermissionError, OSError): + except (PermissionError, OSError) as e: + logger.warning("Failed to check PID %d status: %s", pid, e) return False @@ -114,7 +119,8 @@ def _read_lock_file(lock_path: Path) -> Dict[str, Any]: if not lock_path.exists(): return {} return json.loads(lock_path.read_text(encoding="utf-8")) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to read lock file %s: %s", lock_path, e) return {} @@ -132,7 +138,8 @@ def _calculate_runtime_minutes(timestamp_str: str) -> float: started = datetime.fromisoformat(timestamp_str) elapsed = datetime.now() - started return elapsed.total_seconds() / 60.0 - except (ValueError, TypeError): + except (ValueError, TypeError) as e: + logger.warning("Failed to parse timestamp '%s': %s", timestamp_str, e) return 0.0 @@ -277,7 +284,8 @@ def _write_section_to_all_branches(section_name: str, section_data: Dict, if result.returncode == 0 and result.stdout.strip().isdigit(): return int(result.stdout.strip()) return 0 - except Exception: + except Exception as e: + logger.error("Failed to write agent_status section to branches: %s", e) return 0 @@ -315,7 +323,8 @@ def push_agent_status_dashboard() -> bool: return success_count > 0 - except Exception: + except Exception as e: + logger.error("Failed to push agent status dashboard: %s", e) return False diff --git a/src/aipass/prax/apps/handlers/dashboard/operations.py b/src/aipass/prax/apps/handlers/dashboard/operations.py index 23515b87..87fe502d 100644 --- a/src/aipass/prax/apps/handlers/dashboard/operations.py +++ b/src/aipass/prax/apps/handlers/dashboard/operations.py @@ -14,10 +14,13 @@ All business logic for dashboard file operations. """ import json +import logging from datetime import datetime from pathlib import Path from typing import Dict +logger = logging.getLogger(__name__) + from aipass.prax.apps.handlers.json import json_handler # Resolve prax root from this file's location @@ -60,8 +63,9 @@ def load_dashboard(branch_path: Path, template: Dict) -> Dict: return new_dashboard try: data = json.loads(content) - except json.JSONDecodeError: + except json.JSONDecodeError as e: # Corrupted file - recreate from template + logger.warning("Corrupted dashboard JSON for %s, recreating from template: %s", branch_path.name, e) new_dashboard = template.copy() new_dashboard["branch"] = branch_path.name.upper() return new_dashboard @@ -124,8 +128,8 @@ def create_fresh_dashboard(branch_path: Path) -> Dict: ) dashboard["last_updated"] = now return dashboard - except (json.JSONDecodeError, OSError): - pass # Fall through to hardcoded + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to load dashboard template file %s, falling back to hardcoded: %s", template_file, e) # Fallback: hardcoded (backward compat) now = datetime.now().isoformat() @@ -322,7 +326,8 @@ def write_section(branch_path: Path, section_name: str, section_data: Dict) -> b if content: try: dashboard = json.loads(content) - except json.JSONDecodeError: + except json.JSONDecodeError as e: + logger.warning("Corrupted dashboard JSON at %s, creating fresh: %s", dashboard_path, e) dashboard = create_fresh_dashboard(branch_path) else: dashboard = create_fresh_dashboard(branch_path) @@ -352,5 +357,6 @@ def write_section(branch_path: Path, section_name: str, section_data: Dict) -> b return saved - except Exception: + except Exception as e: + logger.error("Failed to write section '%s' for branch %s: %s", section_name, branch_path.name, e) return False diff --git a/src/aipass/prax/apps/handlers/dashboard/refresh.py b/src/aipass/prax/apps/handlers/dashboard/refresh.py index f412aa7e..95d1ae49 100644 --- a/src/aipass/prax/apps/handlers/dashboard/refresh.py +++ b/src/aipass/prax/apps/handlers/dashboard/refresh.py @@ -14,10 +14,13 @@ AIPASS owns all dashboards - services only maintain their central files. """ import json +import logging from pathlib import Path from datetime import datetime from typing import Dict, List, Optional +logger = logging.getLogger(__name__) + # Same-package imports allowed from .operations import create_fresh_dashboard, save_dashboard @@ -138,8 +141,8 @@ def _extract_memory_bank_section(centrals: Dict, branch_path: Path) -> Dict: cursor.execute("SELECT COUNT(*) FROM embeddings") local_vectors = cursor.fetchone()[0] conn.close() - except Exception: - pass + except Exception as e: + logger.warning("Failed to read ChromaDB vectors from %s: %s", chroma_dir, e) # Pull last_updated from central if available mb_data = centrals.get("memory_bank", {}) @@ -249,6 +252,7 @@ def refresh_all_dashboards() -> Dict: try: branch_paths = _load_branch_paths() except Exception as e: + logger.error("Failed to load branch paths: %s", e) return { "status": "error", "branches_updated": 0, @@ -281,8 +285,8 @@ def refresh_all_dashboards() -> Dict: existing_commons = existing.get("sections", {}).get("commons_activity") if existing_commons: dashboard["sections"]["commons_activity"] = existing_commons - except (json.JSONDecodeError, OSError): - pass + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to read existing commons data for %s: %s", branch_name, e) # Preserve write-through sections not managed by refresh (e.g. agent_status) existing_path = branch_path / "DASHBOARD.local.json" @@ -292,8 +296,8 @@ def refresh_all_dashboards() -> Dict: for key, value in existing.get("sections", {}).items(): if key not in REFRESH_MANAGED_SECTIONS and key not in dashboard["sections"]: dashboard["sections"][key] = value - except (json.JSONDecodeError, OSError): - pass + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to preserve write-through sections for %s: %s", branch_name, e) # Calculate quick status dashboard["quick_status"] = _calculate_quick_status(dashboard["sections"]) @@ -303,6 +307,7 @@ def refresh_all_dashboards() -> Dict: branches_updated += 1 except Exception as e: + logger.warning("Dashboard refresh failed for %s: %s", branch_name, e) errors.append(f"{branch_name}: {str(e)}") branches_failed += 1 @@ -360,8 +365,8 @@ def refresh_single_dashboard(branch_path: Path) -> Dict: existing_commons = existing.get("sections", {}).get("commons_activity") if existing_commons: dashboard["sections"]["commons_activity"] = existing_commons - except (json.JSONDecodeError, OSError): - pass + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to read existing commons data for %s: %s", branch_name, e) # Preserve write-through sections not managed by refresh (e.g. agent_status) existing_path = branch_path / "DASHBOARD.local.json" @@ -371,8 +376,8 @@ def refresh_single_dashboard(branch_path: Path) -> Dict: for key, value in existing.get("sections", {}).items(): if key not in REFRESH_MANAGED_SECTIONS and key not in dashboard["sections"]: dashboard["sections"][key] = value - except (json.JSONDecodeError, OSError): - pass + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to preserve write-through sections for %s: %s", branch_name, e) dashboard["quick_status"] = _calculate_quick_status(dashboard["sections"]) @@ -381,4 +386,5 @@ def refresh_single_dashboard(branch_path: Path) -> Dict: return {"status": "success", "branch": branch_name} except Exception as e: + logger.error("Single dashboard refresh failed for %s: %s", branch_name, e) return {"status": "error", "branch": branch_name, "error": str(e)} diff --git a/src/aipass/prax/apps/handlers/dashboard/template_differ.py b/src/aipass/prax/apps/handlers/dashboard/template_differ.py index dd031341..d1f5bf41 100644 --- a/src/aipass/prax/apps/handlers/dashboard/template_differ.py +++ b/src/aipass/prax/apps/handlers/dashboard/template_differ.py @@ -23,11 +23,14 @@ Independence: """ import json +import logging from pathlib import Path from typing import Dict, Any, Optional from aipass.prax.apps.handlers.json import json_handler +logger = logging.getLogger(__name__) + # ============================================================================= # PATH RESOLUTION # ============================================================================= @@ -103,7 +106,8 @@ def _diff_branch(branch_name: str, branch_path: Path, template: dict) -> Dict[st try: data = json.loads(content) - except json.JSONDecodeError: + except json.JSONDecodeError as e: + logger.warning("Invalid JSON in dashboard for diff: %s", e) result["status"] = "invalid_json" return result @@ -188,6 +192,7 @@ def diff_dashboard_template(branch_name: Optional[str] = None) -> Dict[str, Any] try: template = json.loads(TEMPLATE_FILE.read_text()) except json.JSONDecodeError as e: + logger.error("Invalid template JSON: %s", e) return {"error": f"Invalid template JSON: {e}", "branches": [], "summary": {}} # Load branch registry @@ -197,6 +202,7 @@ def diff_dashboard_template(branch_name: Optional[str] = None) -> Dict[str, Any] try: registry = json.loads(BRANCH_REGISTRY.read_text()) except json.JSONDecodeError as e: + logger.error("Invalid registry JSON: %s", e) return {"error": f"Invalid registry JSON: {e}", "branches": [], "summary": {}} # Filter to active branches diff --git a/src/aipass/prax/apps/handlers/dashboard/template_pusher.py b/src/aipass/prax/apps/handlers/dashboard/template_pusher.py index 3e505bed..7069aee8 100644 --- a/src/aipass/prax/apps/handlers/dashboard/template_pusher.py +++ b/src/aipass/prax/apps/handlers/dashboard/template_pusher.py @@ -25,10 +25,13 @@ Independence: import json import copy +import logging from pathlib import Path from datetime import datetime from typing import Dict, Any, List +logger = logging.getLogger(__name__) + from aipass.prax.apps.handlers.json import json_handler # ============================================================================= @@ -217,6 +220,7 @@ def push_dashboard_template(dry_run: bool = False) -> Dict[str, Any]: try: template = json.loads(TEMPLATE_FILE.read_text()) except json.JSONDecodeError as e: + logger.error("Invalid template JSON: %s", e) result["success"] = False result["errors"].append(f"Invalid template JSON: {e}") return result @@ -230,6 +234,7 @@ def push_dashboard_template(dry_run: bool = False) -> Dict[str, Any]: try: registry = json.loads(BRANCH_REGISTRY.read_text()) except json.JSONDecodeError as e: + logger.error("Invalid registry JSON: %s", e) result["success"] = False result["errors"].append(f"Invalid registry JSON: {e}") return result @@ -269,6 +274,7 @@ def push_dashboard_template(dry_run: bool = False) -> Dict[str, Any]: tmp_path.write_text(json.dumps(new_dashboard, indent=2)) tmp_path.rename(dashboard_path) except OSError as e: + logger.warning("Failed to create dashboard for %s: %s", branch_name, e) result["errors"].append(f"{branch_name}: failed to create dashboard: {e}") result["branches_skipped"] += 1 continue @@ -295,6 +301,7 @@ def push_dashboard_template(dry_run: bool = False) -> Dict[str, Any]: tmp_path.write_text(json.dumps(new_dashboard, indent=2)) tmp_path.rename(dashboard_path) except OSError as e: + logger.warning("Failed to write dashboard for %s: %s", branch_name, e) result["errors"].append(f"{branch_name}: failed to write dashboard: {e}") result["branches_skipped"] += 1 continue @@ -307,7 +314,8 @@ def push_dashboard_template(dry_run: bool = False) -> Dict[str, Any]: try: data = json.loads(content) - except json.JSONDecodeError: + except json.JSONDecodeError as e: + logger.warning("Invalid JSON in dashboard for %s: %s", branch_name, e) result["branches_skipped"] += 1 result["errors"].append(f"{branch_name}: invalid JSON in dashboard, skipped") continue @@ -375,6 +383,7 @@ def push_dashboard_template(dry_run: bool = False) -> Dict[str, Any]: tmp_path.write_text(json.dumps(data, indent=2)) tmp_path.rename(dashboard_path) except OSError as e: + logger.warning("Failed to write updated dashboard for %s: %s", branch_name, e) result["errors"].append(f"{branch_name}: failed to write dashboard: {e}") result["branches_skipped"] += 1 continue @@ -421,7 +430,8 @@ def _update_version_file(branches_pushed: List[str]) -> bool: VERSION_FILE.write_text(json.dumps(version_data, indent=2) + "\n") return True - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to update dashboard version file %s: %s", VERSION_FILE, e) return False @@ -456,7 +466,8 @@ def get_template_status() -> Dict[str, Any]: status["changes"] = data.get("changes", []) status["last_push"] = data.get("last_push") status["last_push_branches"] = data.get("last_push_branches", []) - except (json.JSONDecodeError, OSError): + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to read dashboard version file %s: %s", VERSION_FILE, e) status["version"] = "error reading version file" return status diff --git a/src/aipass/prax/apps/handlers/discovery/scanner.py b/src/aipass/prax/apps/handlers/discovery/scanner.py index d5e46c98..d503593d 100755 --- a/src/aipass/prax/apps/handlers/discovery/scanner.py +++ b/src/aipass/prax/apps/handlers/discovery/scanner.py @@ -12,11 +12,15 @@ PRAX Discovery Scanner Safe directory scanning for Python module discovery. """ +import logging + from pathlib import Path from datetime import datetime, timezone from typing import Dict, Any +logger = logging.getLogger(__name__) + # Import from prax config from aipass.prax.apps.handlers.config.load import ( ECOSYSTEM_ROOT, @@ -65,12 +69,10 @@ def scan_directory_safely(directory: Path, modules: Dict, max_depth: int = 10): elif item.is_dir(): scan_directory_safely(item, modules, max_depth - 1) - except PermissionError: - # Silent operation - permission denied directories are skipped - pass - except Exception: - # Silent operation - errors are skipped - pass + except PermissionError as e: + logger.warning(f"[scanner] Permission denied scanning directory {directory}: {e}") + except Exception as e: + logger.warning(f"[scanner] Error scanning directory {directory}: {e}") def discover_python_modules() -> Dict[str, Dict[str, Any]]: """Discover all Python modules in the ecosystem diff --git a/src/aipass/prax/apps/handlers/discovery/watcher.py b/src/aipass/prax/apps/handlers/discovery/watcher.py index 919f33c7..320ccd63 100755 --- a/src/aipass/prax/apps/handlers/discovery/watcher.py +++ b/src/aipass/prax/apps/handlers/discovery/watcher.py @@ -15,6 +15,9 @@ Memory file handling moved to MEMORY_BANK's own watcher. No console output - follows 3-tier handler pattern. """ +import logging +logger = logging.getLogger(__name__) + from pathlib import Path from datetime import datetime, timezone @@ -42,7 +45,8 @@ from aipass.prax.apps.handlers.json import json_handler try: from aipass.trigger.apps.modules.core import trigger _HAS_TRIGGER = True -except ImportError: +except ImportError as e: + logger.info(f"[watcher] trigger module not available, falling back: {e}") trigger = None # type: ignore[assignment] _HAS_TRIGGER = False @@ -72,8 +76,9 @@ class PythonFileWatcher(FileSystemEventHandler): # Add new module to registry try: relative_path = py_file.relative_to(ECOSYSTEM_ROOT) - except ValueError: + except ValueError as e: # File is outside ECOSYSTEM_ROOT, skip + logger.info(f"[watcher] Path outside ecosystem root, skipping {py_file}: {e}") return modules[module_name] = { @@ -98,8 +103,8 @@ class PythonFileWatcher(FileSystemEventHandler): file_path=str(py_file), relative_path=str(relative_path) ) - except (OSError, Exception): - pass # Trigger fire failed, continue silently + except (OSError, Exception) as e: + logger.warning(f"[watcher] trigger.fire('module_discovered') failed for {module_name}: {e}") def start_file_watcher(): diff --git a/src/aipass/prax/apps/handlers/json/json_handler.py b/src/aipass/prax/apps/handlers/json/json_handler.py index 8800e3bb..e3c378aa 100755 --- a/src/aipass/prax/apps/handlers/json/json_handler.py +++ b/src/aipass/prax/apps/handlers/json/json_handler.py @@ -14,11 +14,14 @@ Never manually create JSONs - they build themselves. """ import json +import logging from pathlib import Path from datetime import datetime from typing import Dict, Any, Optional import inspect +logger = logging.getLogger(__name__) + # Resolve paths relative to this file (no hardcoded paths) _HANDLER_DIR = Path(__file__).resolve().parent # .../handlers/json/ _HANDLERS_DIR = _HANDLER_DIR.parent # .../handlers/ @@ -48,7 +51,8 @@ def _get_caller_module_name() -> str: # Fallback return "unknown" - except Exception: + except Exception as e: + logger.warning("json_handler: failed to detect caller module name: %s", e) return "unknown" @@ -69,7 +73,8 @@ def load_template(json_type: str, module_name: str) -> Any: template_str = template_str.replace("{{TIMESTAMP}}", datetime.now().date().isoformat()) return json.loads(template_str) - except Exception: + except Exception as e: + logger.warning("json_handler: failed to load template '%s' for module '%s': %s", json_type, module_name, e) return None @@ -114,8 +119,8 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: return True else: pass # Corrupted - will regenerate - except Exception: - pass # Unreadable - will regenerate + except Exception as e: + logger.warning("json_handler: unreadable json for '%s/%s', will regenerate: %s", module_name, json_type, e) template = load_template(json_type, module_name) if template is None: @@ -125,7 +130,8 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: with open(json_path, 'w', encoding='utf-8') as f: json.dump(template, f, indent=2, ensure_ascii=False) return True - except Exception: + except Exception as e: + logger.error("json_handler: failed to write json file '%s/%s': %s", module_name, json_type, e) return False @@ -139,7 +145,8 @@ def load_json(module_name: str, json_type: str) -> Optional[Any]: try: with open(json_path, 'r', encoding='utf-8') as f: return json.load(f) - except Exception: + except Exception as e: + logger.warning("json_handler: failed to load json '%s/%s': %s", module_name, json_type, e) return None @@ -157,7 +164,8 @@ def save_json(module_name: str, json_type: str, data: Any) -> bool: with open(json_path, 'w', encoding='utf-8') as f: json.dump(data, f, indent=2, ensure_ascii=False) return True - except Exception: + except Exception as e: + logger.error("json_handler: failed to save json '%s/%s': %s", module_name, json_type, e) return False diff --git a/src/aipass/prax/apps/handlers/logging/introspection.py b/src/aipass/prax/apps/handlers/logging/introspection.py index 1b2ab1ed..5676ac1a 100755 --- a/src/aipass/prax/apps/handlers/logging/introspection.py +++ b/src/aipass/prax/apps/handlers/logging/introspection.py @@ -13,6 +13,8 @@ Stack introspection for detecting calling modules and branch paths. Used by logger_setup.py to route logs to correct files. """ +import logging +logger = logging.getLogger(__name__) from pathlib import Path from typing import Optional @@ -110,7 +112,7 @@ def detect_branch_from_path(module_path: str) -> Optional[str]: json_handler.log_operation("introspection_resolved", {"module_path": module_path, "branch": branch}) return branch except ValueError: - pass + logger.info("Path %s is not relative to aipass package root", module_path) # Fallback: src/{branch}/... for branches outside src/aipass/ (e.g., commons) try: @@ -120,6 +122,6 @@ def detect_branch_from_path(module_path: str) -> Optional[str]: json_handler.log_operation("introspection_resolved", {"module_path": module_path, "branch": branch, "outside_aipass": True}) return branch except ValueError: - pass + logger.info("Path %s is not relative to src root", module_path) return None diff --git a/src/aipass/prax/apps/handlers/logging/log_watchdog.py b/src/aipass/prax/apps/handlers/logging/log_watchdog.py index dafe4aa7..9e25e099 100644 --- a/src/aipass/prax/apps/handlers/logging/log_watchdog.py +++ b/src/aipass/prax/apps/handlers/logging/log_watchdog.py @@ -21,6 +21,8 @@ Two modes: - enforce: Truncate oversized files to keep last max_lines """ +import logging +logger = logging.getLogger(__name__) import sys from datetime import datetime from pathlib import Path @@ -73,7 +75,8 @@ def _count_lines(filepath: Path) -> int: try: with open(filepath, 'rb') as f: return sum(1 for _ in f) - except OSError: + except OSError as e: + logger.info("Failed to count lines in %s: %s", filepath, e) return 0 @@ -89,7 +92,8 @@ def _get_file_size_kb(filepath: Path) -> float: """ try: return filepath.stat().st_size / 1024.0 - except OSError: + except OSError as e: + logger.info("Failed to stat file %s: %s", filepath, e) return 0.0 @@ -186,7 +190,8 @@ def truncate_log_file(filepath: Path, keep_lines: int = DEFAULT_MAX_LINES) -> Tu return original_count, keep_lines + 1 # +1 for marker line - except OSError: + except OSError as e: + logger.warning("Failed to truncate log file %s: %s", filepath, e) return 0, 0 diff --git a/src/aipass/prax/apps/handlers/logging/operations.py b/src/aipass/prax/apps/handlers/logging/operations.py index c3638dc1..70c9ed95 100755 --- a/src/aipass/prax/apps/handlers/logging/operations.py +++ b/src/aipass/prax/apps/handlers/logging/operations.py @@ -23,7 +23,7 @@ from aipass.prax.apps.handlers.config.load import PRAX_JSON_DIR from aipass.prax.apps.handlers.logging.direct import get_direct_logger from aipass.prax.apps.handlers.json import json_handler -_logger = get_direct_logger() +logger = get_direct_logger() # Module constants MODULE_NAME = "prax_logger" @@ -50,7 +50,8 @@ def log_operation(message: str, data: Optional[Dict] = None): try: with open(LOG_FILE, 'r', encoding='utf-8') as f: log_entries = json.load(f) - except Exception: + except Exception as e: + logger.warning("Failed to load log file %s, resetting entries: %s", LOG_FILE, e) log_entries = [] # Add new entry @@ -91,6 +92,6 @@ def create_config_file(): try: with open(CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(default_config, f, indent=2, ensure_ascii=False) - _logger.info("Config file created: %s", CONFIG_FILE) + logger.info("Config file created: %s", CONFIG_FILE) except Exception as e: - _logger.warning("Failed to create config file: %s", e) + logger.warning("Failed to create config file: %s", e) diff --git a/src/aipass/prax/apps/handlers/logging/setup.py b/src/aipass/prax/apps/handlers/logging/setup.py index d0839b16..5c3d13b7 100755 --- a/src/aipass/prax/apps/handlers/logging/setup.py +++ b/src/aipass/prax/apps/handlers/logging/setup.py @@ -14,6 +14,7 @@ Handles dual logging (system-wide + branch-local) and terminal output. """ import logging +logger = logging.getLogger(__name__) from pathlib import Path from typing import Dict, Optional from logging.handlers import RotatingFileHandler @@ -36,6 +37,7 @@ from aipass.prax.apps.handlers.logging.introspection import ( from aipass.prax.apps.handlers.json import json_handler # Global state for logging system +logger = logging.getLogger(__name__) _system_logger: Optional[logging.Logger] = None _captured_loggers: Dict[str, logging.Logger] = {} _terminal_output_enabled = False @@ -46,7 +48,8 @@ try: from aipass.prax.apps.handlers.logging.terminal.formatting import create_terminal_handler from aipass.prax.apps.handlers.logging.terminal.filtering import should_display_terminal _terminal_module_available = True -except ImportError: +except ImportError as e: + logger.info(f"[setup] Terminal handler modules not available: {e}") create_terminal_handler = None # type: ignore[assignment] should_display_terminal = None # type: ignore[assignment] @@ -60,8 +63,7 @@ def _safe_rotating_handler(log_file: Path, max_bytes: int, backup_count: int) -> _system_logger.warning(f"Self-healed missing log directory: {parent}") return RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count, encoding='utf-8') except OSError as e: - if _system_logger: - _system_logger.error(f"Log handler failed for {log_file}: {e}") + logger.error("Log handler failed for %s: %s", log_file, e) return logging.NullHandler() diff --git a/src/aipass/prax/apps/handlers/logging/terminal/filtering.py b/src/aipass/prax/apps/handlers/logging/terminal/filtering.py index a2288f89..9ce523ab 100755 --- a/src/aipass/prax/apps/handlers/logging/terminal/filtering.py +++ b/src/aipass/prax/apps/handlers/logging/terminal/filtering.py @@ -12,6 +12,8 @@ PRAX Terminal Filtering Filters terminal output to reduce noise from internal modules. """ +import logging +logger = logging.getLogger(__name__) from pathlib import Path import json @@ -46,8 +48,8 @@ def load_filtered_modules() -> Set[str]: with open(CONFIG_FILE, 'r', encoding='utf-8') as f: config = json.load(f) return set(config.get('filtered_modules', DEFAULT_FILTERED_MODULES)) - except Exception: - pass + except Exception as e: + logger.warning("Failed to load terminal filter config %s: %s", CONFIG_FILE, e) return DEFAULT_FILTERED_MODULES diff --git a/src/aipass/prax/apps/handlers/monitoring/event_queue.py b/src/aipass/prax/apps/handlers/monitoring/event_queue.py index 33655db9..dffc654b 100644 --- a/src/aipass/prax/apps/handlers/monitoring/event_queue.py +++ b/src/aipass/prax/apps/handlers/monitoring/event_queue.py @@ -8,6 +8,9 @@ """Thread-safe event coordination for monitoring system""" +import logging +logger = logging.getLogger(__name__) + from pathlib import Path from queue import Empty, PriorityQueue @@ -67,7 +70,8 @@ class MonitoringQueue: if len(self.recent_events) > 100: self.recent_events.pop(0) return True - except Exception: + except Exception as e: + logger.warning(f"[event_queue] Failed to enqueue event (type={event.event_type}, branch={event.branch}): {e}") return False return False @@ -76,6 +80,7 @@ class MonitoringQueue: try: return self.queue.get(timeout=timeout) except Empty: + logger.info("[event_queue] Queue empty on dequeue (timeout=%.1f)", timeout) return None def flush(self): @@ -85,6 +90,7 @@ class MonitoringQueue: try: self.queue.get_nowait() except Empty: + logger.info("[event_queue] Flush complete (queue drained)") break self.recent_events.clear() diff --git a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py index 3d0d6ed3..d1829f36 100644 --- a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py +++ b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py @@ -32,7 +32,8 @@ logger = get_direct_logger() try: from aipass.trigger.apps.modules.core import trigger _trigger_available = True -except ImportError: +except ImportError as e: + logger.info(f"[monitor] trigger module not available, falling back: {e}") trigger = None # type: ignore[assignment] _trigger_available = False @@ -162,7 +163,8 @@ class MonitoringFileHandler(FileSystemEventHandler): for line in reversed(lines): try: entry = _json.loads(line) - except _json.JSONDecodeError: + except _json.JSONDecodeError as e: + logger.info(f"[monitor] Skipping malformed JSONL line: {e}") continue entry_type = entry.get('type', '') diff --git a/src/aipass/prax/apps/handlers/monitoring/log_watcher.py b/src/aipass/prax/apps/handlers/monitoring/log_watcher.py index 3f47cabb..a778afad 100644 --- a/src/aipass/prax/apps/handlers/monitoring/log_watcher.py +++ b/src/aipass/prax/apps/handlers/monitoring/log_watcher.py @@ -38,18 +38,19 @@ from aipass.prax.apps.handlers.config.load import get_system_logs_dir from aipass.prax.apps.handlers.monitoring.event_queue import MonitoringEvent, MonitoringQueue from aipass.prax.apps.handlers.monitoring.branch_detector import detect_branch_from_log +from aipass.prax.apps.handlers.json import json_handler + +logger = get_direct_logger() + # Trigger integration - graceful fallback if trigger not available try: from aipass.trigger.apps.modules.core import trigger HAS_TRIGGER = True -except ImportError: +except ImportError as e: + logger.info("[log_watcher] trigger module not available: %s", e) trigger = None # type: ignore[assignment] HAS_TRIGGER = False -from aipass.prax.apps.handlers.json import json_handler - -logger = get_direct_logger() - def _generate_error_hash(module_name: str, message: str) -> str: """ @@ -636,6 +637,7 @@ if __name__ == '__main__': time.sleep(0.1) except KeyboardInterrupt: + logger.info("[log_watcher] Stopped by user") console.print("\n[yellow]Stopping log watcher...[/yellow]") stop_log_watcher() queue.stop() diff --git a/src/aipass/prax/apps/handlers/monitoring/unified_stream.py b/src/aipass/prax/apps/handlers/monitoring/unified_stream.py index a65f9d62..9616c6a6 100644 --- a/src/aipass/prax/apps/handlers/monitoring/unified_stream.py +++ b/src/aipass/prax/apps/handlers/monitoring/unified_stream.py @@ -18,13 +18,17 @@ Single point for all monitoring terminal output with: from pathlib import Path +import logging +logger = logging.getLogger(__name__) + from datetime import datetime from typing import Optional, Dict, List from threading import Lock try: from aipass.cli.apps.modules import console -except ImportError: +except ImportError as e: + logger.info(f"[unified_stream] CLI console not available, falling back to rich.Console: {e}") from rich.console import Console console = Console() diff --git a/src/aipass/prax/apps/handlers/registry/load.py b/src/aipass/prax/apps/handlers/registry/load.py index bfa1d263..4b5fc0c3 100755 --- a/src/aipass/prax/apps/handlers/registry/load.py +++ b/src/aipass/prax/apps/handlers/registry/load.py @@ -26,12 +26,15 @@ Usage: """ import json +import logging from pathlib import Path from typing import Dict, Any from aipass.prax.apps.handlers.config.load import PRAX_ROOT from aipass.prax.apps.handlers.json import json_handler +logger = logging.getLogger(__name__) + # ============================================= # CONFIGURATION # ============================================= @@ -79,6 +82,6 @@ def load_module_registry() -> Dict[str, Dict[str, Any]]: modules = data.get('modules', {}) json_handler.log_operation("registry_loaded", {"module_count": len(modules)}) return modules - except Exception: - # Silently return empty dict - logging not available at this level + except Exception as e: + logger.warning("load: failed to load module registry from '%s': %s", REGISTRY_FILE, e) return {} diff --git a/src/aipass/prax/apps/handlers/registry/save.py b/src/aipass/prax/apps/handlers/registry/save.py index 7bb8a05d..608e3ede 100755 --- a/src/aipass/prax/apps/handlers/registry/save.py +++ b/src/aipass/prax/apps/handlers/registry/save.py @@ -27,6 +27,7 @@ Usage: """ import json +import logging from pathlib import Path from datetime import datetime, timezone from typing import Dict, Any @@ -34,6 +35,8 @@ from typing import Dict, Any from aipass.prax.apps.handlers.config.load import PRAX_ROOT, ECOSYSTEM_ROOT from aipass.prax.apps.handlers.json import json_handler +logger = logging.getLogger(__name__) + # ============================================= # CONFIGURATION # ============================================= @@ -97,6 +100,6 @@ def save_module_registry(modules: Dict[str, Dict[str, Any]]) -> bool: return True - except Exception: - # Silently return False - logging not available at this level + except Exception as e: + logger.error("save: failed to save module registry to '%s': %s", REGISTRY_FILE, e) return False diff --git a/src/aipass/prax/apps/handlers/watcher/monitor.py b/src/aipass/prax/apps/handlers/watcher/monitor.py index 5c9c7930..61cc4ddf 100755 --- a/src/aipass/prax/apps/handlers/watcher/monitor.py +++ b/src/aipass/prax/apps/handlers/watcher/monitor.py @@ -14,6 +14,9 @@ Monitors all files (including __pycache__, .pyc, etc.) to provide complete visibility into branch modifications. """ +import logging +logger = logging.getLogger(__name__) + from pathlib import Path from typing import List, Callable, Optional, TYPE_CHECKING, Any @@ -28,7 +31,8 @@ try: from watchdog.events import FileSystemEventHandler # type: ignore from watchdog.events import FileSystemEvent # type: ignore WATCHDOG_AVAILABLE = True -except ImportError: +except ImportError as e: + logger.info(f"[monitor] watchdog not available, using placeholders: {e}") WATCHDOG_AVAILABLE = False # Create placeholder classes for when watchdog not available class Observer: # type: ignore diff --git a/src/aipass/prax/apps/modules/dashboard.py b/src/aipass/prax/apps/modules/dashboard.py index 021750c3..50713aeb 100644 --- a/src/aipass/prax/apps/modules/dashboard.py +++ b/src/aipass/prax/apps/modules/dashboard.py @@ -203,6 +203,7 @@ def print_status(): try: branches = get_branch_paths() except Exception as e: + logger.error("Error loading branches: %s", e) error(f"Error loading branches: {e}") return @@ -279,6 +280,7 @@ def _handle_refresh(args: List[str]) -> None: try: branch_path = _resolve_branch_path(args[0]) except FileNotFoundError as e: + logger.warning("Branch path not found: %s", e) error(str(e)) return console.print(f"[dim]Refreshing {branch_path.name.upper()} dashboard...[/dim]") diff --git a/src/aipass/prax/apps/modules/logger.py b/src/aipass/prax/apps/modules/logger.py index 305895b9..5212fb9f 100755 --- a/src/aipass/prax/apps/modules/logger.py +++ b/src/aipass/prax/apps/modules/logger.py @@ -39,9 +39,14 @@ __all__ = [ "DATA_FILE", ] +import logging import sys from typing import Dict, Any +# Stdlib logger for except-block compliance (seedgo requires variable named 'logger') +# SystemLogger methods shadow this with local 'logger = get_system_logger()' which is fine +logger = logging.getLogger(__name__) + # NOTE: CLI imports are done lazily inside functions to avoid circular dependency. # CLI imports prax logger, so prax logger must not import CLI at module level. @@ -98,14 +103,14 @@ class SystemLogger: if not is_file_watcher_active(): try: start_file_watcher() - except OSError: - pass # inotify limit reached, continue without watcher + except OSError as e: + logger.warning("inotify limit reached, continuing without file watcher: %s", e) # Fire startup event (trigger auto-initializes handlers) try: from aipass.trigger.apps.modules.core import trigger trigger.fire('startup') - except (ImportError, OSError): - pass # Trigger not available or inotify full, silent fallback + except (ImportError, OSError) as e: + logger.warning("Trigger startup fire skipped (not available or inotify full): %s", e) def info(self, message, *args, **kwargs): """Log info message to calling module's log file""" @@ -205,6 +210,7 @@ def start_continuous_logging(): ) except KeyboardInterrupt: # Handler re-raises KeyboardInterrupt, we handle cleanup here + logger.info("Logger capture stopped by user") disable_terminal_output() shutdown_logging_system() console.print(f"[{MODULE_NAME}] Logger capture stopped.") @@ -251,7 +257,8 @@ def print_introspection(): """Display module introspection info.""" try: from aipass.cli.apps.modules.display import console - except ImportError: + except ImportError as e: + logger.info("CLI console not available, using rich fallback: %s", e) from rich.console import Console console = Console() diff --git a/src/aipass/prax/apps/modules/monitor.py b/src/aipass/prax/apps/modules/monitor.py index 98df5469..f4bd2b44 100755 --- a/src/aipass/prax/apps/modules/monitor.py +++ b/src/aipass/prax/apps/modules/monitor.py @@ -153,7 +153,8 @@ def _refresh_pid_cache() -> None: name = branch.get("name", "").upper() if name: new_cache[name] = pid - except (ValueError, OSError): + except (ValueError, OSError) as e: + logger.info("[monitor] Skipping dispatch lock %s: %s", lock_path, e) continue with _pid_cache_lock: _pid_cache.clear() @@ -490,6 +491,7 @@ def _interactive_loop(): while _monitoring_active: time.sleep(0.5) except KeyboardInterrupt: + logger.info("[monitor] Stopped by user (passive mode)") console.print("\n[yellow]Stopping monitoring...[/yellow]") return @@ -520,9 +522,11 @@ def _interactive_loop(): console.print("[dim]Type 'help' for available commands[/dim]") except KeyboardInterrupt: + logger.info("[monitor] Stopped by user") console.print("\n[yellow]Stopping monitoring...[/yellow]") break except EOFError: + logger.info("[monitor] EOF received, stopping interactive loop") break diff --git a/src/aipass/prax/apps/prax.py b/src/aipass/prax/apps/prax.py index 40480639..7f885288 100755 --- a/src/aipass/prax/apps/prax.py +++ b/src/aipass/prax/apps/prax.py @@ -63,6 +63,7 @@ def discover_command_modules() -> List[Callable]: command_handlers.append(module.handle_command) except Exception as e: + logger.warning("Failed to load module %s: %s", module_file.name, e) warning(f"Failed to load module {module_file.name}: {e}") return command_handlers @@ -147,6 +148,7 @@ def route_command(command: str, args: List[str], handlers: List[Callable]) -> bo if handler(command, args): return True except Exception as e: + logger.error("Handler failed: %s", e) error(f"Handler failed: {e}") return False diff --git a/src/aipass/spawn/.seedgo/bypass.json b/src/aipass/spawn/.seedgo/bypass.json index 9157c0b3..25f4f591 100644 --- a/src/aipass/spawn/.seedgo/bypass.json +++ b/src/aipass/spawn/.seedgo/bypass.json @@ -34,6 +34,36 @@ "file": "log_structure", "standard": "log_structure", "reason": "Two-tier model managed by Prax — flat logs/ at branch root is correct per PR #63" + }, + { + "file": "apps/modules/core.py", + "standard": "deep_nesting", + "reason": "handle_command() depth 7 — complex argument parsing for spawn create with --role/--traits/--purpose/--template/--registry flags. Nested while loop through args array; extraction would obscure control flow." + }, + { + "file": "apps/handlers/update_ops.py", + "standard": "deep_nesting", + "reason": "update_branch() depth 4 — 10-step orchestration workflow (resolve, load registry, load meta, reconcile, detect changes, dry-run, execute changes, refresh meta, post-flight, return). Nesting serves workflow structure." + }, + { + "file": "apps/handlers/file_ops.py", + "standard": "deep_nesting", + "reason": "regenerate_template_registry() depth 5 — dual-pass tree scan (directories then files) to build registry with correct IDs. Nesting inherent to algorithm." + }, + { + "file": "apps/handlers/meta_ops.py", + "standard": "deep_nesting", + "reason": "save_branch_meta() depth 5 — atomic file write with try-except-cleanup for temp file pattern. generate_branch_meta() depth 5 — triple-loop adoption path matching (path-first, then hash). Both justified." + }, + { + "file": "apps/handlers/sync_templates_ops.py", + "standard": "deep_nesting", + "reason": "sync_templates() depth 4 — linear workflow loading owners, iterating managed files, checking hashes. Minimal branching, straightforward responsibility." + }, + { + "file": "apps/handlers/reconcile.py", + "standard": "deep_nesting", + "reason": "reconcile_branch_state() depth 6 — three independent checks (tracked files, tracked dirs, untracked files) with iterdir loops and hash computation. Structure reflects algorithm necessity." } ], "notes": { diff --git a/src/aipass/spawn/apps/handlers/delete_ops.py b/src/aipass/spawn/apps/handlers/delete_ops.py index f59bc35b..192c352f 100644 --- a/src/aipass/spawn/apps/handlers/delete_ops.py +++ b/src/aipass/spawn/apps/handlers/delete_ops.py @@ -117,7 +117,8 @@ def delete_branch( if confirm and not dry_run: try: answer = input("Are you sure? (y/N): ").strip().lower() - except (EOFError, KeyboardInterrupt): + except (EOFError, KeyboardInterrupt) as e: + logger.warning("Delete confirmation prompt interrupted: %s", e) answer = "" if answer != "y": return { diff --git a/src/aipass/spawn/apps/handlers/file_ops.py b/src/aipass/spawn/apps/handlers/file_ops.py index 62fd4097..6659456f 100644 --- a/src/aipass/spawn/apps/handlers/file_ops.py +++ b/src/aipass/spawn/apps/handlers/file_ops.py @@ -69,7 +69,8 @@ def copy_template(template_dir, target_dir, replacements): content = replace_placeholders(content, replacements) dest.write_text(content, encoding="utf-8") copied.append(str(dest_rel)) - except (UnicodeDecodeError, UnicodeEncodeError): + except (UnicodeDecodeError, UnicodeEncodeError) as e: + logger.warning(f"[spawn] Text read/write failed for {dest_rel}, falling back to binary copy: {e}") shutil.copy2(item, dest) copied.append(f"{dest_rel} (binary)") @@ -186,15 +187,16 @@ def regenerate_template_registry(target_dir): try: content = item.read_bytes() content_hash = hashlib.sha256(content).hexdigest()[:16] - except (IOError, PermissionError): + except (IOError, PermissionError) as e: + logger.error(f"[spawn] Failed to read file for hashing {item}: {e}") content_hash = "unreadable" has_placeholder = False try: text = item.read_text(encoding="utf-8") has_placeholder = "{{" in text and "}}" in text - except (UnicodeDecodeError, IOError): - pass + except (UnicodeDecodeError, IOError) as e: + logger.warning(f"[spawn] Failed to check placeholders in {item}: {e}") files[file_id] = { "path": str(rel), diff --git a/src/aipass/spawn/apps/handlers/json/json_handler.py b/src/aipass/spawn/apps/handlers/json/json_handler.py index 361184b7..c6b18ffa 100644 --- a/src/aipass/spawn/apps/handlers/json/json_handler.py +++ b/src/aipass/spawn/apps/handlers/json/json_handler.py @@ -17,6 +17,8 @@ from datetime import datetime from pathlib import Path from typing import Any, Dict, Optional +from aipass.prax import logger + _SPAWN_ROOT = Path(__file__).resolve().parents[3] _JSON_DIR = _SPAWN_ROOT / "spawn_json" @@ -24,8 +26,9 @@ _JSON_DIR = _SPAWN_ROOT / "spawn_json" def read_json(file_path: Path) -> Optional[dict]: """Read and parse a JSON file.""" try: - return json.loads(file_path.read_text()) - except (json.JSONDecodeError, FileNotFoundError): + return json.loads(file_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, FileNotFoundError) as e: + logger.warning("Failed to read JSON from %s: %s", file_path, e) return None @@ -33,9 +36,10 @@ def write_json(file_path: Path, data: Any, indent: int = 2) -> bool: """Write data to a JSON file.""" try: file_path.parent.mkdir(parents=True, exist_ok=True) - file_path.write_text(json.dumps(data, indent=indent) + "\n") + file_path.write_text(json.dumps(data, indent=indent) + "\n", encoding="utf-8") return True - except OSError: + except OSError as e: + logger.error("Failed to write JSON to %s: %s", file_path, e) return False @@ -72,8 +76,9 @@ def log_operation(operation: str, data: Dict[str, Any] | None = None, module_nam log: list = [] if log_path.exists(): try: - log = json.loads(log_path.read_text()) - except (json.JSONDecodeError, OSError): + log = json.loads(log_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError) as e: + logger.warning("Failed to read operation log %s, resetting: %s", log_path, e) log = [] entry: Dict[str, Any] = { @@ -90,7 +95,8 @@ def log_operation(operation: str, data: Dict[str, Any] | None = None, module_nam log = log[-100:] try: - log_path.write_text(json.dumps(log, indent=2) + "\n") + log_path.write_text(json.dumps(log, indent=2) + "\n", encoding="utf-8") return True - except OSError: + except OSError as e: + logger.error("Failed to write operation log %s: %s", log_path, e) return False diff --git a/src/aipass/spawn/apps/handlers/meta_ops.py b/src/aipass/spawn/apps/handlers/meta_ops.py index 4930d433..9452fd16 100644 --- a/src/aipass/spawn/apps/handlers/meta_ops.py +++ b/src/aipass/spawn/apps/handlers/meta_ops.py @@ -166,8 +166,8 @@ def save_branch_meta(branch_dir: Path, meta: dict) -> bool: if tmp_path.exists(): try: tmp_path.unlink() - except OSError: - pass + except OSError as e: + logger.warning(f"Failed to clean up temp file {tmp_path}: {e}") return False diff --git a/src/aipass/spawn/apps/handlers/placeholders.py b/src/aipass/spawn/apps/handlers/placeholders.py index e774f107..c8b3ebce 100644 --- a/src/aipass/spawn/apps/handlers/placeholders.py +++ b/src/aipass/spawn/apps/handlers/placeholders.py @@ -13,6 +13,7 @@ import re from datetime import datetime from pathlib import Path +from aipass.prax.apps.modules.logger import system_logger as logger from aipass.spawn.apps.handlers.registry import find_registry @@ -47,7 +48,8 @@ def build_replacements_dict(target_dir, branch_name, **overrides): if registry_path.exists(): data = json.loads(registry_path.read_text(encoding="utf-8")) registry_id = data.get("metadata", {}).get("id", "") - except Exception: + except Exception as e: + logger.warning(f"Failed to read registry ID for placeholders: {e}") registry_id = "" replacements = { @@ -87,7 +89,8 @@ def validate_no_placeholders(target_dir): continue try: content = file_path.read_text(encoding="utf-8") - except (UnicodeDecodeError, PermissionError): + except (UnicodeDecodeError, PermissionError) as e: + logger.warning(f"Could not read file for placeholder validation {file_path}: {e}") continue found = pattern.findall(content) diff --git a/src/aipass/spawn/apps/handlers/reconcile.py b/src/aipass/spawn/apps/handlers/reconcile.py index 8b58e144..b0c9105f 100644 --- a/src/aipass/spawn/apps/handlers/reconcile.py +++ b/src/aipass/spawn/apps/handlers/reconcile.py @@ -35,7 +35,8 @@ def _compute_hash(file_path: Path) -> str: for chunk in iter(lambda: f.read(8192), b""): sha256.update(chunk) return sha256.hexdigest()[:12] - except (IOError, PermissionError): + except (IOError, PermissionError) as e: + logger.warning("Failed to compute hash for %s: %s", file_path, e) return "" diff --git a/src/aipass/spawn/apps/handlers/registry.py b/src/aipass/spawn/apps/handlers/registry.py index c36b86b9..c98bf828 100644 --- a/src/aipass/spawn/apps/handlers/registry.py +++ b/src/aipass/spawn/apps/handlers/registry.py @@ -13,6 +13,7 @@ import os from datetime import datetime from pathlib import Path +from aipass.prax import logger from aipass.spawn.apps.handlers.json import json_handler @@ -112,7 +113,8 @@ def load_registry(registry_path): try: data = json.loads(registry_path.read_text(encoding="utf-8")) return data - except (json.JSONDecodeError, IOError): + except (json.JSONDecodeError, IOError) as e: + logger.warning("Failed to load registry from %s: %s", registry_path, e) return { "metadata": { "version": "1.0.0", @@ -154,7 +156,8 @@ def save_registry(registry_path, data): encoding="utf-8", ) return True - except (IOError, TypeError): + except (IOError, TypeError) as e: + logger.error("Failed to save registry to %s: %s", registry_path, e) return False diff --git a/src/aipass/spawn/apps/handlers/sync_registry_ops.py b/src/aipass/spawn/apps/handlers/sync_registry_ops.py index 9f876281..727ae04d 100644 --- a/src/aipass/spawn/apps/handlers/sync_registry_ops.py +++ b/src/aipass/spawn/apps/handlers/sync_registry_ops.py @@ -191,8 +191,8 @@ def sync_registry(fix: bool = False) -> dict: try: passport = json.loads(passport_path.read_text(encoding="utf-8")) citizen_class = passport.get("identity", {}).get("citizen_class", "builder") - except (json.JSONDecodeError, IOError): - pass + except (json.JSONDecodeError, IOError) as e: + logger.warning(f"Failed to read passport for citizen class detection ({name}): {e}") # Load template registry for that class (fall back to builder if unknown) try: diff --git a/src/aipass/spawn/apps/handlers/sync_templates_ops.py b/src/aipass/spawn/apps/handlers/sync_templates_ops.py index 52785a04..5ecbcf08 100644 --- a/src/aipass/spawn/apps/handlers/sync_templates_ops.py +++ b/src/aipass/spawn/apps/handlers/sync_templates_ops.py @@ -148,5 +148,6 @@ def _file_hash(filepath: Path) -> str: try: content = filepath.read_bytes() return hashlib.sha256(content).hexdigest()[:12] - except IOError: + except IOError as e: + logger.warning(f"Failed to hash file {filepath}: {e}") return "" diff --git a/src/aipass/spawn/apps/handlers/update_ops.py b/src/aipass/spawn/apps/handlers/update_ops.py index d3121fa2..deb77dd2 100644 --- a/src/aipass/spawn/apps/handlers/update_ops.py +++ b/src/aipass/spawn/apps/handlers/update_ops.py @@ -322,7 +322,8 @@ def _read_citizen_class(branch_dir: Path) -> str: try: data = json.loads(passport_path.read_text(encoding="utf-8")) return data.get("identity", {}).get("citizen_class", "builder") - except (json.JSONDecodeError, IOError): + except (json.JSONDecodeError, IOError) as e: + logger.warning(f"[update] Failed to read citizen_class from passport {passport_path}: {e}") return "builder" @@ -422,8 +423,9 @@ def _execute_addition( content = source.read_text(encoding="utf-8") content = replace_placeholders(content, replacements) dest.write_text(content, encoding="utf-8") - except (UnicodeDecodeError, UnicodeEncodeError): + except (UnicodeDecodeError, UnicodeEncodeError) as e: # Binary file — copy directly + logger.warning(f"[update] Text read/write failed for {template_path}, falling back to binary copy: {e}") shutil.copy2(source, dest) if trace: diff --git a/src/aipass/spawn/apps/modules/core.py b/src/aipass/spawn/apps/modules/core.py index 4078c550..69d02b94 100644 --- a/src/aipass/spawn/apps/modules/core.py +++ b/src/aipass/spawn/apps/modules/core.py @@ -26,7 +26,8 @@ from aipass.prax import logger try: from aipass.cli.apps.modules.display import console -except ImportError: +except ImportError as e: + logger.warning("Failed to import aipass.cli.apps.modules.display, falling back to rich.console: %s", e) from rich.console import Console console = Console() diff --git a/src/aipass/spawn/apps/modules/passport.py b/src/aipass/spawn/apps/modules/passport.py index d481e2e1..7424098a 100644 --- a/src/aipass/spawn/apps/modules/passport.py +++ b/src/aipass/spawn/apps/modules/passport.py @@ -15,6 +15,7 @@ All implementation logic lives in apps/handlers/passport_ops.py. import argparse from aipass.cli.apps.modules import console, error, warning +from aipass.prax import logger from aipass.spawn.apps.handlers.passport_ops import grant_passport from aipass.spawn.apps.handlers.json import json_handler diff --git a/src/aipass/spawn/apps/spawn.py b/src/aipass/spawn/apps/spawn.py index cfad2f2f..2f28f839 100644 --- a/src/aipass/spawn/apps/spawn.py +++ b/src/aipass/spawn/apps/spawn.py @@ -193,6 +193,7 @@ if __name__ == "__main__": try: sys.exit(main()) except KeyboardInterrupt: + logger.warning("SPAWN interrupted by user (KeyboardInterrupt)") console.print("\n\nOperation cancelled by user") sys.exit(0) except Exception as e: diff --git a/src/aipass/trigger/apps/handlers/error_registry.py b/src/aipass/trigger/apps/handlers/error_registry.py index 60f2c129..e1c01dd6 100644 --- a/src/aipass/trigger/apps/handlers/error_registry.py +++ b/src/aipass/trigger/apps/handlers/error_registry.py @@ -502,8 +502,8 @@ def _load_registry() -> dict: data = json.loads(REGISTRY_FILE.read_text(encoding='utf-8')) if isinstance(data, dict) and 'errors' in data: return data - except Exception: - pass + except Exception as exc: + logger.warning("Failed to load error registry: %s", exc) return { "errors": {}, "metadata": { @@ -621,7 +621,8 @@ def report( json_handler.log_operation("error_registered", {"fingerprint": fingerprint[:12], "count": 1}) return result - except Exception: + except Exception as exc: + logger.warning("Failed to report error for component '%s': %s", component, exc) return { "error_type": error_type, "message": message, @@ -670,7 +671,8 @@ def query( return entries[:limit] - except Exception: + except Exception as exc: + logger.warning("Failed to query error registry: %s", exc) return [] @@ -706,7 +708,8 @@ def update_status(fingerprint: str, new_status: str, reason: str = "") -> bool: return _save_registry(registry) - except Exception: + except Exception as exc: + logger.warning("Failed to update status for fingerprint '%s': %s", fingerprint, exc) return False @@ -725,7 +728,8 @@ def get_entry(fingerprint: str) -> Optional[dict]: try: registry = _load_registry() return _find_entry(registry, fingerprint) - except Exception: + except Exception as exc: + logger.warning("Failed to get entry for fingerprint '%s': %s", fingerprint, exc) return None @@ -762,7 +766,8 @@ def clear_resolved(days: int = 7) -> int: return removed - except Exception: + except Exception as exc: + logger.warning("Failed to clear resolved entries: %s", exc) return 0 @@ -801,7 +806,8 @@ def get_stats() -> dict: "by_severity": by_severity } - except Exception: + except Exception as exc: + logger.warning("Failed to get error registry stats: %s", exc) return { "total": 0, "by_status": {}, @@ -832,7 +838,8 @@ def update_source_fix_status(fingerprint: str, fix_status: str) -> bool: return False entry["source_fix_status"] = fix_status return _save_registry(registry) - except Exception: + except Exception as exc: + logger.warning("Failed to update source fix status for '%s': %s", fingerprint, exc) return False diff --git a/src/aipass/trigger/apps/handlers/error_reporter.py b/src/aipass/trigger/apps/handlers/error_reporter.py index 2ab01f77..28ae97c3 100644 --- a/src/aipass/trigger/apps/handlers/error_reporter.py +++ b/src/aipass/trigger/apps/handlers/error_reporter.py @@ -166,8 +166,8 @@ def report_error( count=error_count, ) result["dispatched"] = True - except Exception: - pass + except Exception as exc: + logger.warning("Failed to fire error_detected event for '%s': %s", component, exc) json_handler.log_operation("error_reported", {"branch": component, "error_type": error_type}) return result diff --git a/src/aipass/trigger/apps/handlers/events/bulletin_created.py b/src/aipass/trigger/apps/handlers/events/bulletin_created.py index b0d13176..dc476476 100644 --- a/src/aipass/trigger/apps/handlers/events/bulletin_created.py +++ b/src/aipass/trigger/apps/handlers/events/bulletin_created.py @@ -22,9 +22,10 @@ Event data expected: """ import json -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List +from aipass.trigger.apps.config import TRIGGER_ROOT from aipass.trigger.apps.handlers.json import json_handler def _find_repo_root() -> Path: @@ -41,6 +42,19 @@ _REPO_ROOT = _find_repo_root() BRANCH_REGISTRY = _REPO_ROOT / "BRANCH_REGISTRY.json" BULLETINS_PATH = _REPO_ROOT / "BULLETINS.central.json" +_HANDLER_LOG = TRIGGER_ROOT / "logs" / "bulletin_handler.log" + + +def _log_warning(message: str) -> None: + """Log warning to file (event handlers cannot import Prax logger - causes recursion).""" + try: + _HANDLER_LOG.parent.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + with open(_HANDLER_LOG, 'a', encoding='utf-8') as f: + f.write(f"{ts} | WARNING | {message}\n") + except Exception: + pass + def _load_branch_registry() -> List[Dict]: """ @@ -55,7 +69,8 @@ def _load_branch_registry() -> List[Dict]: return [] data = json.loads(BRANCH_REGISTRY.read_text()) return data.get("branches", []) - except Exception: + except Exception as exc: + _log_warning(f"load branch registry failed: {exc}") return [] @@ -71,7 +86,8 @@ def _load_bulletins() -> List[Dict]: return [] data = json.loads(BULLETINS_PATH.read_text()) return data.get("bulletins", []) - except Exception: + except Exception as exc: + _log_warning(f"load bulletins failed: {exc}") return [] @@ -112,8 +128,8 @@ def _load_dashboard(branch_path: Path) -> Dict: "pending_ack": [] } return data - except Exception: - pass + except Exception as exc: + _log_warning(f"parse dashboard JSON for {branch_path}: {exc}") # Create minimal dashboard structure return { @@ -144,7 +160,8 @@ def _save_dashboard(branch_path: Path, dashboard: Dict) -> bool: dashboard["last_refreshed"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") dashboard_path.write_text(json.dumps(dashboard, indent=2)) return True - except Exception: + except Exception as exc: + _log_warning(f"save dashboard to {branch_path}: {exc}") return False @@ -193,11 +210,12 @@ def _propagate_bulletins_to_branches() -> None: # Save dashboard _save_dashboard(branch_path, dashboard) - except Exception: + except Exception as exc: + _log_warning(f"propagate to branch {branch_path_str}: {exc}") continue - except Exception: - pass + except Exception as exc: + _log_warning(f"bulletin propagation failed: {exc}") def handle_bulletin_created( @@ -232,5 +250,5 @@ def handle_bulletin_created( json_handler.log_operation("bulletin_event", {"success": True}) - except Exception: - pass + except Exception as exc: + _log_warning(f"handle_bulletin_created failed: {exc}") diff --git a/src/aipass/trigger/apps/handlers/events/error_detected.py b/src/aipass/trigger/apps/handlers/events/error_detected.py index 9ce5c345..323da50e 100644 --- a/src/aipass/trigger/apps/handlers/events/error_detected.py +++ b/src/aipass/trigger/apps/handlers/events/error_detected.py @@ -35,12 +35,26 @@ Architecture (Medic v2): import json import time -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from typing import Any, Callable, Dict, List, Optional from aipass.trigger.apps.config import TRIGGER_ROOT from aipass.trigger.apps.handlers.json import json_handler +_HANDLER_LOG = TRIGGER_ROOT / "logs" / "error_detected_handler.log" + + +def _log_warning(message: str) -> None: + """Log warning to file (event handlers cannot import Prax logger - causes recursion).""" + try: + _HANDLER_LOG.parent.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + with open(_HANDLER_LOG, 'a', encoding='utf-8') as f: + f.write(f"{ts} | WARNING | {message}\n") + except Exception: + pass + + def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json).""" current = Path(__file__).resolve().parent @@ -101,7 +115,8 @@ def _is_medic_enabled() -> bool: if TRIGGER_CONFIG_FILE.exists(): data = json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8')) return bool(data.get('config', {}).get('medic_enabled', True)) - except Exception: + except Exception as exc: + _log_warning(f"_is_medic_enabled config read failed: {exc}") return True # Default to enabled on read failure return True @@ -124,7 +139,8 @@ def _is_branch_muted(branch_name: str) -> bool: data = json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8')) muted = data.get('config', {}).get('muted_branches', []) return branch_name.lower() in [b.lower() for b in muted] - except Exception: + except Exception as exc: + _log_warning(f"_is_branch_muted config read failed: {exc}") return False return False @@ -154,7 +170,8 @@ def _get_registered_emails() -> set: if BRANCH_REGISTRY_FILE.exists(): data = json.loads(BRANCH_REGISTRY_FILE.read_text(encoding='utf-8')) return {b["email"] for b in data.get("branches", [])} - except Exception: + except Exception as exc: + _log_warning(f"_get_registered_emails registry read failed: {exc}") return set() return set() @@ -231,7 +248,8 @@ def _read_log_context(log_path: str, error_message: str, context_lines: int = 2) end = min(len(lines), target_idx + context_lines + 1) context = lines[start:end] return "\n".join(context) - except Exception: + except Exception as exc: + _log_warning(f"_read_log_context failed for {log_path}: {exc}") return "" @@ -393,7 +411,8 @@ def handle_error_detected( f"Medic OFF - suppressed dispatch for {branch}: " f"{module} - {message[:100]}\n" ) - except Exception: + except Exception as exc: + _log_warning(f"medic OFF suppression log write failed: {exc}") return # Can't log suppression, but still skip dispatch return @@ -408,7 +427,8 @@ def handle_error_detected( f"Branch muted - suppressed dispatch for {branch}: " f"{module} - {message[:100]}\n" ) - except Exception: + except Exception as exc: + _log_warning(f"branch muted suppression log write failed: {exc}") return # Can't log suppression, but still skip dispatch return @@ -424,7 +444,8 @@ def handle_error_detected( f"First occurrence (count={count}) - waiting for pattern: " f"{branch}: {module} - {message[:100]}\n" ) - except Exception: + except Exception as exc: + _log_warning(f"first occurrence suppression log write failed: {exc}") return # Can't log, but still skip dispatch return @@ -452,7 +473,8 @@ def handle_error_detected( f"Unknown branch skipped: {recipient} - " f"{module}: {message[:100]}\n" ) - except Exception: + except Exception as exc: + _log_warning(f"unknown branch suppression log write failed: {exc}") return # Can't log skip, still don't dispatch return @@ -469,8 +491,8 @@ def handle_error_detected( f"Circuit breaker OPEN - suppressed dispatch for {branch}: " f"{module} - {message[:100]}\n" ) - except Exception: - pass + except Exception as exc: + _log_warning(f"circuit breaker suppression log write failed: {exc}") return if not registry_should_dispatch(fingerprint): @@ -483,8 +505,8 @@ def handle_error_detected( f"Backoff active for fingerprint {fingerprint[:12]}: " f"{recipient} - {module}, skipping\n" ) - except Exception: - pass + except Exception as exc: + _log_warning(f"backoff rate log write failed: {exc}") return else: # Legacy fallback: per-branch rate limiting (Medic v1) @@ -502,8 +524,8 @@ def handle_error_detected( f"Rate limited: {recipient} has {recent_count} " f"recent dispatches, skipping\n" ) - except Exception: - pass + except Exception as exc: + _log_warning(f"legacy rate limit log write failed: {exc}") return # Default timestamp to now if not provided @@ -555,5 +577,6 @@ def handle_error_detected( # Legacy: per-branch rate limiting _record_dispatch(recipient) - except Exception: + except Exception as exc: + _log_warning(f"handle_error_detected failed: {exc}") return # Silent failure - handler must not raise diff --git a/src/aipass/trigger/apps/handlers/events/error_logged.py b/src/aipass/trigger/apps/handlers/events/error_logged.py index 34ce0a53..418917bc 100644 --- a/src/aipass/trigger/apps/handlers/events/error_logged.py +++ b/src/aipass/trigger/apps/handlers/events/error_logged.py @@ -29,12 +29,26 @@ Event data expected: import json import time -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict, List from aipass.trigger.apps.config import TRIGGER_ROOT from aipass.trigger.apps.handlers.json import json_handler +_HANDLER_LOG = TRIGGER_ROOT / "logs" / "error_logged_handler.log" + + +def _log_warning(message: str) -> None: + """Log warning to file (event handlers cannot import Prax logger - causes recursion).""" + try: + _HANDLER_LOG.parent.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + with open(_HANDLER_LOG, 'a', encoding='utf-8') as f: + f.write(f"{ts} | WARNING | {message}\n") + except Exception: + pass + + def _find_repo_root() -> Path: """Walk up from this file to find the repo root (contains AIPASS_REGISTRY.json).""" current = Path(__file__).resolve().parent @@ -68,7 +82,8 @@ def _is_medic_enabled() -> bool: if TRIGGER_CONFIG_FILE.exists(): data = json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8')) return bool(data.get('config', {}).get('medic_enabled', True)) - except Exception: + except Exception as exc: + _log_warning(f"_is_medic_enabled config read failed: {exc}") return True return True @@ -89,7 +104,8 @@ def _is_branch_muted(branch_name: str) -> bool: data = json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8')) muted = data.get('config', {}).get('muted_branches', []) return branch_name.lower() in [b.lower() for b in muted] - except Exception: + except Exception as exc: + _log_warning(f"_is_branch_muted config read failed: {exc}") return False return False @@ -104,7 +120,8 @@ def _get_registered_emails() -> set: if BRANCH_REGISTRY_FILE.exists(): data = json.loads(BRANCH_REGISTRY_FILE.read_text(encoding='utf-8')) return {b["email"] for b in data.get("branches", [])} - except Exception: + except Exception as exc: + _log_warning(f"_get_registered_emails registry read failed: {exc}") return set() return set() @@ -159,7 +176,8 @@ def _log_suppression(reason: str, branch: str, source_module: str, message: str) f"{reason} - suppressed dispatch for {branch}: " f"{source_module} - {message[:100]}\n" ) - except Exception: + except Exception as exc: + _log_warning(f"_log_suppression write failed: {exc}") return @@ -317,5 +335,6 @@ def handle_error_logged( json_handler.log_operation("error_logged_event", {"success": True}) - except Exception: + except Exception as exc: + _log_warning(f"handle_error_logged failed: {exc}") return diff --git a/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py b/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py index 95e76864..52882154 100644 --- a/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py +++ b/src/aipass/trigger/apps/handlers/events/memory_threshold_exceeded.py @@ -24,15 +24,29 @@ Event data expected: - timestamp: When detected """ -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from typing import Any +from aipass.trigger.apps.config import TRIGGER_ROOT from aipass.trigger.apps.handlers.json import json_handler # Path resolution not needed - this handler uses only event data passed in kwargs +_HANDLER_LOG = TRIGGER_ROOT / "logs" / "memory_threshold_handler.log" + + +def _log_warning(message: str) -> None: + """Log warning to file (event handlers cannot import Prax logger - causes recursion).""" + try: + _HANDLER_LOG.parent.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + with open(_HANDLER_LOG, 'a', encoding='utf-8') as f: + f.write(f"{ts} | WARNING | {message}\n") + except Exception: + pass + def _build_compression_message( branch: str, @@ -160,5 +174,5 @@ def handle_memory_threshold_exceeded( json_handler.log_operation("memory_threshold_event", {"success": True}) - except Exception: - pass + except Exception as exc: + _log_warning(f"handle memory threshold exceeded failed: {exc}") diff --git a/src/aipass/trigger/apps/handlers/events/startup.py b/src/aipass/trigger/apps/handlers/events/startup.py index fd6d87ed..102c8806 100644 --- a/src/aipass/trigger/apps/handlers/events/startup.py +++ b/src/aipass/trigger/apps/handlers/events/startup.py @@ -21,7 +21,7 @@ import json import hashlib import time from pathlib import Path -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any, Callable, Dict, List, Optional, Set from aipass.trigger.apps.config import TRIGGER_ROOT from aipass.trigger.apps.handlers.json import json_handler @@ -38,6 +38,19 @@ MAX_ERRORS_PER_SCAN = 50 # Stop after this many new errors found MAX_FILE_SIZE_BYTES = 512_000 # Skip files larger than 500KB SCAN_TIME_BUDGET_SECONDS = 5.0 # Abort entire scan after this many seconds +_HANDLER_LOG = TRIGGER_ROOT / "logs" / "startup_handler.log" + + +def _log_warning(message: str) -> None: + """Log warning to file (event handlers cannot import Prax logger - causes recursion).""" + try: + _HANDLER_LOG.parent.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + with open(_HANDLER_LOG, 'a', encoding='utf-8') as f: + f.write(f"{ts} | WARNING | {message}\n") + except Exception: + pass + def _load_trigger_data() -> Dict[str, Any]: """Load trigger_data.json with error_catchup section.""" @@ -53,7 +66,8 @@ def _load_trigger_data() -> Dict[str, Any]: 'max_lookback_hours': MAX_LOOKBACK_HOURS } return data - except Exception: + except Exception as exc: + _log_warning(f"load trigger data failed: {exc}") return { 'error_catchup': { 'last_scan_timestamp': None, @@ -78,7 +92,8 @@ def _save_trigger_data(data: Dict[str, Any]) -> None: TRIGGER_DATA_FILE.parent.mkdir(parents=True, exist_ok=True) with open(TRIGGER_DATA_FILE, 'w') as f: json.dump(data, f, indent=2) - except Exception: + except Exception as exc: + _log_warning(f"save trigger data failed: {exc}") return @@ -92,7 +107,8 @@ def _log_suppression(reason: str) -> None: SUPPRESSED_LOG.parent.mkdir(parents=True, exist_ok=True) with open(SUPPRESSED_LOG, 'a') as f: f.write(f"{datetime.now().isoformat()} | error_catchup: {reason}\n") - except Exception: + except Exception as exc: + _log_warning(f"log suppression write failed: {exc}") return @@ -144,7 +160,8 @@ def _parse_log_line(log_line: str) -> Optional[Dict[str, str]]: } return None - except Exception: + except Exception as exc: + _log_warning(f"parse log line failed: {exc}") return None @@ -177,7 +194,8 @@ def _detect_branch_from_log(log_file: str) -> str: if '_' in name: return name.split('_')[0].upper() return name.upper() - except Exception: + except Exception as exc: + _log_warning(f"detect branch from log failed: {exc}") return 'UNKNOWN' @@ -223,7 +241,8 @@ def _scan_system_logs_for_errors( if file_size > MAX_FILE_SIZE_BYTES: files_skipped_size += 1 continue - except Exception: + except Exception as exc: + _log_warning(f"stat log file {log_file}: {exc}") continue try: @@ -279,7 +298,8 @@ def _scan_system_logs_for_errors( if len(errors) >= MAX_ERRORS_PER_SCAN: break - except Exception: + except Exception as exc: + _log_warning(f"scan log file {log_file}: {exc}") continue if files_skipped_size > 0: @@ -312,8 +332,8 @@ def _run_error_catchup(fire_event: Optional[Callable[..., None]] = None) -> None if last_scan: try: since_ts = datetime.fromisoformat(last_scan) - except Exception: - pass + except Exception as exc: + _log_warning(f"parse last_scan_timestamp '{last_scan}': {exc}") processed_hashes = set(catchup.get('processed_hashes', [])) @@ -336,7 +356,8 @@ def _run_error_catchup(fire_event: Optional[Callable[..., None]] = None) -> None json_handler.log_operation("startup_catchup", {"errors_found": len(errors)}) - except Exception: + except Exception as exc: + _log_warning(f"error catchup scan failed: {exc}") return @@ -351,7 +372,8 @@ def _run_memory_bank_check() -> None: check_and_rollover() except ImportError: return # Memory not available - except Exception: + except Exception as exc: + _log_warning(f"memory bank check failed: {exc}") return diff --git a/src/aipass/trigger/apps/handlers/json/json_handler.py b/src/aipass/trigger/apps/handlers/json/json_handler.py index 89ccd6aa..2b78aa20 100644 --- a/src/aipass/trigger/apps/handlers/json/json_handler.py +++ b/src/aipass/trigger/apps/handlers/json/json_handler.py @@ -8,11 +8,23 @@ import json from pathlib import Path -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, List, Any, Optional import inspect # Infrastructure +_LOG_FILE = Path(__file__).parent.parent.parent.parent / "logs" / "json_handler.log" + + +def _log_warning(msg: str) -> None: + """File-based warning logger to avoid circular imports with prax.""" + try: + _LOG_FILE.parent.mkdir(parents=True, exist_ok=True) + with open(_LOG_FILE, 'a', encoding='utf-8') as f: + f.write(f"{datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} | WARNING | {msg}\n") + except Exception: + pass + # Constants TRIGGER_ROOT = Path(__file__).resolve().parents[3] @@ -100,8 +112,9 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: if validate_json_structure(data, json_type): return True # If corrupted, fall through to regenerate - except Exception: + except Exception as exc: # If unreadable, fall through to regenerate + _log_warning(f"ensure_json_exists failed for {module_name}_{json_type}: {exc}") pass template = load_template(json_type, module_name) diff --git a/src/aipass/trigger/apps/handlers/log_watcher.py b/src/aipass/trigger/apps/handlers/log_watcher.py index e0e9a2c5..a781cfde 100644 --- a/src/aipass/trigger/apps/handlers/log_watcher.py +++ b/src/aipass/trigger/apps/handlers/log_watcher.py @@ -130,7 +130,8 @@ def _load_seen_hashes() -> None: data = json.loads(TRIGGER_DATA_FILE.read_text(encoding='utf-8')) stored = data.get('seen_error_hashes', []) _seen_error_hashes = set(stored) - except Exception: + except Exception as exc: + logger.warning("Failed to load seen hashes: %s", exc) _seen_error_hashes = set() # Start fresh on read failure @@ -150,7 +151,8 @@ def _save_seen_hashes() -> None: TRIGGER_DATA_FILE.write_text( json.dumps(data, indent=2), encoding='utf-8' ) - except Exception: + except Exception as exc: + logger.warning("Failed to save seen hashes: %s", exc) return # Write failure - hashes remain in memory only @@ -194,7 +196,8 @@ def _save_log_positions(positions: Dict[str, int]) -> None: TRIGGER_DATA_FILE.write_text( json.dumps(data, indent=2), encoding='utf-8' ) - except Exception: + except Exception as exc: + logger.warning("Failed to save log positions: %s", exc) return # Write failure - positions remain in memory only @@ -226,7 +229,8 @@ def _is_stale_entry(timestamp_str: str) -> bool: try: entry_time = datetime.strptime(timestamp_str.strip(), fmt) return entry_time < cutoff - except ValueError: + except ValueError as exc: + logger.warning("Failed to parse timestamp '%s': %s", timestamp_str.strip(), exc) continue # If we can't parse the timestamp, treat as STALE to avoid re-flagging @@ -291,7 +295,8 @@ def _detect_branch_from_path(log_path: str) -> str: if i + 2 < len(parts) and parts[i + 2] == 'logs': return parts[i + 1].upper() return 'UNKNOWN' - except Exception: + except Exception as exc: + logger.warning("Failed to detect branch from path '%s': %s", log_path, exc) return 'UNKNOWN' @@ -346,7 +351,8 @@ def _parse_prax_log_line(log_line: str) -> Optional[Dict[str, str]]: } return None - except Exception: + except Exception as exc: + logger.warning("Failed to parse log line: %s", exc) return None @@ -462,7 +468,8 @@ class BranchLogWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else _save_log_positions(self.log_positions) self._position_save_counter = 0 - except Exception: + except Exception as exc: + logger.warning("Failed to read log file '%s': %s", file_path, exc) return # Read failure on this event - skip without raising def _process_log_line(self, log_line: str, log_path: str) -> None: @@ -572,7 +579,8 @@ class BranchLogWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else "(branch=%s, module=%s)", branch, module ) - except Exception: + except Exception as exc: + logger.warning("Failed to process log line from '%s': %s", log_path, exc) return # Parse/fire failure on this line - skip without raising def initialize_positions(self) -> None: @@ -604,7 +612,8 @@ class BranchLogWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else self.log_positions[file_path] = saved_pos else: self.log_positions[file_path] = current_size - except Exception: + except Exception as exc: + logger.warning("Failed to initialize position for branch log '%s': %s", log_file, exc) continue # Skip unreadable log file # System-level logs under ~/system_logs/ @@ -618,7 +627,8 @@ class BranchLogWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else self.log_positions[file_path] = saved_pos else: self.log_positions[file_path] = current_size - except Exception: + except Exception as exc: + logger.warning("Failed to initialize position for system log '%s': %s", log_file, exc) continue # Skip unreadable log file diff --git a/src/aipass/trigger/apps/handlers/medic_state.py b/src/aipass/trigger/apps/handlers/medic_state.py index a9be0a8b..e3180238 100644 --- a/src/aipass/trigger/apps/handlers/medic_state.py +++ b/src/aipass/trigger/apps/handlers/medic_state.py @@ -21,9 +21,12 @@ from datetime import datetime from pathlib import Path from typing import Any, Dict, List +from aipass.prax.apps.modules.logger import get_direct_logger from aipass.trigger.apps.config import TRIGGER_ROOT from aipass.trigger.apps.handlers.json import json_handler +logger = get_direct_logger() + TRIGGER_CONFIG_FILE = TRIGGER_ROOT / "trigger_json" / "trigger_config.json" MEDIC_SUPPRESSED_LOG = TRIGGER_ROOT / "logs" / "medic_suppressed.log" RATE_LIMITED_LOG = TRIGGER_ROOT / "logs" / "rate_limited.log" @@ -39,7 +42,8 @@ def read_config() -> dict: try: if TRIGGER_CONFIG_FILE.exists(): return json.loads(TRIGGER_CONFIG_FILE.read_text(encoding='utf-8')) - except Exception: + except Exception as exc: + logger.warning("read_config failed: %s", exc) return {} return {} @@ -60,7 +64,8 @@ def write_config(data: dict) -> bool: json.dumps(data, indent=2), encoding='utf-8' ) return True - except Exception: + except Exception as exc: + logger.warning("write_config failed: %s", exc) return False @@ -201,7 +206,8 @@ def get_suppression_stats() -> Dict[str, Any]: if lines: last_line = lines[-1] last_suppressed = last_line.split(' | ')[0] if ' | ' in last_line else "unknown" - except Exception: + except Exception as exc: + logger.warning("get_suppression_stats failed: %s", exc) return {'suppressed_count': 0, 'last_suppressed': 'error reading log'} return { @@ -226,7 +232,8 @@ def get_rate_limit_stats() -> Dict[str, Any]: if lines: last_line = lines[-1] last_dispatch = last_line.split(' | ')[0] if ' | ' in last_line else "unknown" - except Exception: + except Exception as exc: + logger.warning("get_rate_limit_stats failed: %s", exc) return {'rate_limited_count': 0, 'last_rate_limited': 'error reading log'} return { diff --git a/src/aipass/trigger/apps/handlers/watchers/log_watcher.py b/src/aipass/trigger/apps/handlers/watchers/log_watcher.py index 2481743f..0cbff534 100644 --- a/src/aipass/trigger/apps/handlers/watchers/log_watcher.py +++ b/src/aipass/trigger/apps/handlers/watchers/log_watcher.py @@ -86,7 +86,8 @@ def _detect_branch_from_log(log_file: str) -> str: parts = name.split('_') return parts[0].upper() return name.upper() - except Exception: + except Exception as exc: + logger.warning("Failed to detect branch from log '%s': %s", log_file, exc) return 'UNKNOWN' @@ -225,8 +226,8 @@ class LogFileWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else o self.log_positions[file_path] = f.tell() - except Exception: - pass + except Exception as exc: + logger.warning("Failed to read log file '%s': %s", file_path, exc) def _process_log_line(self, branch: str, log_line: str, log_file: str) -> None: """ @@ -263,8 +264,8 @@ class LogFileWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else o trigger.fire('warning_logged', **event_data) json_handler.log_operation("system_log_event", {"level": level, "module": module_name}) - except Exception: - pass + except Exception as exc: + logger.warning("Failed to process log line from '%s': %s", log_file, exc) def initialize_positions(self) -> None: """ @@ -278,8 +279,8 @@ class LogFileWatcher(WatchdogFileSystemEventHandler if WATCHDOG_AVAILABLE else o for log_file in SYSTEM_LOGS_DIR.glob("*.log"): try: self.log_positions[str(log_file)] = log_file.stat().st_size - except Exception: - pass + except Exception as exc: + logger.warning("Failed to initialize position for '%s': %s", log_file, exc) def start_log_watcher() -> Any: diff --git a/src/commons/.seedgo/bypass.json b/src/commons/.seedgo/bypass.json index 298c42e6..d479162f 100644 --- a/src/commons/.seedgo/bypass.json +++ b/src/commons/.seedgo/bypass.json @@ -15,7 +15,16 @@ {"file": "apps/modules/explore.py", "standard": "introspection", "reason": "Action command — explore with no args discovers hints"}, {"file": "apps/modules/leaderboard.py", "standard": "introspection", "reason": "Action command — leaderboard with no args shows rankings"}, {"file": "apps/modules/commons_identity.py", "standard": "introspection", "reason": "Identity utility module — not a user-facing command"}, - {"file": "apps/commons.py", "standard": "stderr_routing", "lines": [255], "reason": "False positive — [yellow] section header in help text, not a warning message"} + {"file": "apps/commons.py", "standard": "stderr_routing", "lines": [255], "reason": "False positive — [yellow] section header in help text, not a warning message"}, + {"file": "apps/commons.py", "standard": "deep_nesting", "reason": "main() depth 5 — app initialization flow: ensure_database, discover_modules, route_command. Legitimate entry point control flow."}, + {"file": "apps/handlers/feed/feed_ops.py", "standard": "deep_nesting", "reason": "format_time_ago() depth 5 — tight utility with try/except + if/elif for time delta calculation. Single purpose."}, + {"file": "apps/handlers/feed/activity_ops.py", "standard": "deep_nesting", "reason": "_relative_time() depth 4 — utility for timestamp formatting. Duplicate of feed_ops format_time_ago but tightly scoped."}, + {"file": "apps/handlers/rooms/notification_ops.py", "standard": "deep_nesting", "reason": "_set_notification_level() depth 5 — shared notification logic (watch/mute/track). Private helper, tight coupling justified."}, + {"file": "apps/handlers/posts/post_ops.py", "standard": "deep_nesting", "reason": "create_post() depth 4 — multi-step workflow (parse args, validate room, insert, mentions, sync FTS, increment count). High cohesion."}, + {"file": "apps/handlers/posts/search_ops.py", "standard": "deep_nesting", "reason": "_parse_search_args() depth 5 — tightly scoped flag parsing loop. Private utility correctly extracted from run_search."}, + {"file": "apps/handlers/posts/comment_ops.py", "standard": "deep_nesting", "reason": "add_comment() depth 4 — multi-step workflow (parse, validate, dedup, insert, update counts, mentions, FTS). High cohesion."}, + {"file": "apps/handlers/database/db.py", "standard": "deep_nesting", "reason": "retry_on_locked() depth 4 — resilient retry pattern for SQLite locking. Correct module location, well-encapsulated."}, + {"file": "apps/handlers/database/central_writer.py", "standard": "deep_nesting", "reason": "aggregate_branch_stats() depth 4 — aggregation loop over branches with per-branch error handling. Appropriate structure."} ], "notes": { "usage": "Add entries to 'bypass' list to exclude specific violations", diff --git a/src/commons/apps/commons.py b/src/commons/apps/commons.py index 4aecfed9..e8d1603e 100644 --- a/src/commons/apps/commons.py +++ b/src/commons/apps/commons.py @@ -360,6 +360,7 @@ if __name__ == "__main__": try: sys.exit(main()) except BrokenPipeError: + logger.warning("[commons] Broken pipe") import os try: sys.stdout.close() diff --git a/src/commons/apps/handlers/activity/activity_ops.py b/src/commons/apps/handlers/activity/activity_ops.py index 80bf10f0..7bf28901 100644 --- a/src/commons/apps/handlers/activity/activity_ops.py +++ b/src/commons/apps/handlers/activity/activity_ops.py @@ -56,6 +56,7 @@ def _relative_time(timestamp_str: str) -> str: days = total_seconds // 86400 return f"{days}d ago" except (ValueError, TypeError): + logger.warning("[activity_ops] Failed to parse timestamp for relative time") return "unknown" @@ -104,6 +105,7 @@ def run_activity(args: List[str]) -> dict: limit = int(args[i + 1]) limit = max(1, min(100, limit)) except ValueError: + logger.warning("[activity_ops] Invalid --limit value") return {"success": False, "error": "Limit must be a number"} i += 2 elif args[i] == "--room" and i + 1 < len(args): @@ -156,7 +158,7 @@ def run_activity(args: List[str]) -> dict: conn = None except Exception as e: - logger.error(f"Activity feed error: {e}") + logger.error(f"[activity_ops] Activity feed query failed: {e}") if conn: close_db(conn) return {"success": False, "error": str(e)} diff --git a/src/commons/apps/handlers/artifacts/artifact_ops.py b/src/commons/apps/handlers/artifacts/artifact_ops.py index 33b0f93d..09dd3328 100644 --- a/src/commons/apps/handlers/artifacts/artifact_ops.py +++ b/src/commons/apps/handlers/artifacts/artifact_ops.py @@ -48,6 +48,7 @@ def _validate_metadata(metadata_str: str) -> Optional[dict]: try: data = json.loads(metadata_str) except (json.JSONDecodeError, TypeError): + logger.warning("[artifact_ops] Invalid metadata JSON string") return None if not isinstance(data, dict): @@ -75,6 +76,7 @@ def _resolve_branch_name(mention: str) -> Optional[str]: return name return None except Exception: + logger.error("[artifact_ops] Failed to resolve branch name from registry") return None @@ -261,6 +263,7 @@ def inspect_artifact(args: List[str]) -> dict: try: artifact_id = int(filtered_args[0]) except ValueError: + logger.warning("[artifact_ops] Non-numeric artifact ID provided for inspect") return {"success": False, "error": "Artifact ID must be a number"} try: @@ -289,6 +292,7 @@ def inspect_artifact(args: List[str]) -> dict: try: metadata = json.loads(artifact["metadata"]) if artifact["metadata"] else {} except (json.JSONDecodeError, TypeError): + logger.warning("[artifact_ops] Failed to parse artifact metadata JSON") metadata = {} artifact["_parsed_metadata"] = metadata @@ -397,6 +401,7 @@ def sign_artifact(args: List[str]) -> dict: try: pending_id = int(args[0]) except ValueError: + logger.warning("[artifact_ops] Non-numeric pending ID provided for sign") return {"success": False, "error": "Pending ID must be a number"} from commons.apps.modules.commons_identity import get_caller_branch diff --git a/src/commons/apps/handlers/artifacts/capsule_ops.py b/src/commons/apps/handlers/artifacts/capsule_ops.py index b435f757..6c17f9f7 100644 --- a/src/commons/apps/handlers/artifacts/capsule_ops.py +++ b/src/commons/apps/handlers/artifacts/capsule_ops.py @@ -45,6 +45,7 @@ def seal_capsule(args: List[str]) -> dict: try: days = int(args[2]) except ValueError: + logger.warning("[capsule_ops] Non-numeric days value provided for seal") return {"success": False, "error": "Days must be a number"} days = max(1, min(365, days)) @@ -159,6 +160,7 @@ def open_capsule(args: List[str]) -> dict: try: capsule_id = int(args[0]) except ValueError: + logger.warning("[capsule_ops] Non-numeric capsule ID provided for open") return {"success": False, "error": "Capsule ID must be a number"} from commons.apps.modules.commons_identity import get_caller_branch diff --git a/src/commons/apps/handlers/artifacts/trade_ops.py b/src/commons/apps/handlers/artifacts/trade_ops.py index 2d1cd510..f62ee884 100644 --- a/src/commons/apps/handlers/artifacts/trade_ops.py +++ b/src/commons/apps/handlers/artifacts/trade_ops.py @@ -55,6 +55,7 @@ def _resolve_branch_name(mention: str) -> Optional[str]: return name return None except Exception: + logger.error("[trade_ops] Failed to resolve branch name from registry") return None @@ -125,6 +126,7 @@ def gift_artifact(args: List[str]) -> dict: try: artifact_id = int(args[0]) except ValueError: + logger.warning("[trade_ops] Non-numeric artifact ID provided for gift") return {"success": False, "error": "Artifact ID must be a number"} recipient = _resolve_branch_name(args[1]) @@ -200,6 +202,7 @@ def trade_artifact(args: List[str]) -> dict: your_id = int(args[0]) their_id = int(args[1]) except ValueError: + logger.warning("[trade_ops] Non-numeric artifact IDs provided for trade") return {"success": False, "error": "Artifact IDs must be numbers"} partner = _resolve_branch_name(args[2]) @@ -306,6 +309,7 @@ def drop_item(args: List[str]) -> dict: expires_minutes = int(remaining[i + 1]) expires_minutes = max(1, min(1440, expires_minutes)) except ValueError: + logger.warning("[trade_ops] Non-numeric --expires value provided for drop") return {"success": False, "error": "--expires must be a number (minutes)"} i += 2 else: @@ -380,6 +384,7 @@ def find_item(args: List[str]) -> dict: try: artifact_id = int(args[0]) except ValueError: + logger.warning("[trade_ops] Non-numeric artifact ID provided for find") return {"success": False, "error": "Artifact ID must be a number"} sweep_expired() diff --git a/src/commons/apps/handlers/catchup/catchup_ops.py b/src/commons/apps/handlers/catchup/catchup_ops.py index 39254a06..bdfbca9e 100644 --- a/src/commons/apps/handlers/catchup/catchup_ops.py +++ b/src/commons/apps/handlers/catchup/catchup_ops.py @@ -57,6 +57,7 @@ def _calculate_time_label(last_active: str) -> str: days = hours // 24 return f"{days} days ago" except (ValueError, TypeError): + logger.warning("[catchup_ops] Failed to parse last_active timestamp") return "your last visit" @@ -106,7 +107,7 @@ def run_catchup(args: List[str]) -> dict: conn = None except Exception as e: - logger.error(f"Catchup query failed: {e}") + logger.error(f"[catchup_ops] Catchup query failed: {e}") if conn: close_db(conn) return {"success": False, "error": str(e)} @@ -119,7 +120,7 @@ def run_catchup(args: List[str]) -> dict: nudge = get_onboarding_nudge(conn_nudge, branch_name) close_db(conn_nudge) except Exception: - pass + logger.warning("[catchup_ops] Failed to fetch onboarding nudge") json_handler.log_operation("catchup_run", {"branch": branch_name, "is_first_visit": is_first_visit}) return { diff --git a/src/commons/apps/handlers/central/central_writer.py b/src/commons/apps/handlers/central/central_writer.py index e6382f00..5ca6a6f6 100644 --- a/src/commons/apps/handlers/central/central_writer.py +++ b/src/commons/apps/handlers/central/central_writer.py @@ -102,6 +102,7 @@ def _read_last_checked(branch_path: str) -> str: last_checked = commons.get("last_updated", "") return last_checked if last_checked else epoch except (json.JSONDecodeError, OSError): + logger.warning(f"[central_writer] Failed to read dashboard last_checked for {branch_path}") return epoch diff --git a/src/commons/apps/handlers/comments/comment_ops.py b/src/commons/apps/handlers/comments/comment_ops.py index 72789826..d995c379 100644 --- a/src/commons/apps/handlers/comments/comment_ops.py +++ b/src/commons/apps/handlers/comments/comment_ops.py @@ -61,6 +61,7 @@ def add_comment(args: List[str]) -> dict: try: parent_id = int(args[i + 1]) except ValueError: + logger.warning(f"[comment_ops] Invalid --parent value: {args[i + 1]!r}") return {"success": False, "error": "Invalid --parent value - must be an integer"} i += 2 else: @@ -77,6 +78,7 @@ def add_comment(args: List[str]) -> dict: try: post_id = int(filtered_args[0]) except ValueError: + logger.warning(f"[comment_ops] Invalid post_id for add_comment: {filtered_args[0]!r}") return {"success": False, "error": "Invalid post_id - must be an integer"} content = filtered_args[1] @@ -253,6 +255,7 @@ def vote_on_content(args: List[str]) -> dict: try: target_id = int(args[1]) except ValueError: + logger.warning(f"[comment_ops] Invalid target_id for vote: {args[1]!r}") return {"success": False, "error": "Invalid target_id - must be an integer"} # --- Validate direction --- diff --git a/src/commons/apps/handlers/curation/curation_ops.py b/src/commons/apps/handlers/curation/curation_ops.py index 334d8125..972278fd 100644 --- a/src/commons/apps/handlers/curation/curation_ops.py +++ b/src/commons/apps/handlers/curation/curation_ops.py @@ -63,6 +63,7 @@ def add_react(args: List[str]) -> dict: try: target_id = int(args[1]) except ValueError: + logger.warning("[curation_ops] Non-numeric ID provided for react") return {"success": False, "error": "ID must be a number"} reaction = args[2].lower() @@ -132,6 +133,7 @@ def remove_react(args: List[str]) -> dict: try: target_id = int(args[1]) except ValueError: + logger.warning("[curation_ops] Non-numeric ID provided for unreact") return {"success": False, "error": "ID must be a number"} reaction = args[2].lower() @@ -187,6 +189,7 @@ def show_reactions(args: List[str]) -> dict: try: target_id = int(args[1]) except ValueError: + logger.warning("[curation_ops] Non-numeric ID provided for reactions query") return {"success": False, "error": "ID must be a number"} try: @@ -229,6 +232,7 @@ def pin_post_cmd(args: List[str]) -> dict: try: post_id = int(args[0]) except ValueError: + logger.warning("[curation_ops] Non-numeric post ID provided for pin") return {"success": False, "error": "Post ID must be a number"} caller = get_caller_branch() @@ -292,6 +296,7 @@ def unpin_post_cmd(args: List[str]) -> dict: try: post_id = int(args[0]) except ValueError: + logger.warning("[curation_ops] Non-numeric post ID provided for unpin") return {"success": False, "error": "Post ID must be a number"} caller = get_caller_branch() diff --git a/src/commons/apps/handlers/dashboard/dashboard_writer.py b/src/commons/apps/handlers/dashboard/dashboard_writer.py index 0b9f3e84..bf46f0df 100644 --- a/src/commons/apps/handlers/dashboard/dashboard_writer.py +++ b/src/commons/apps/handlers/dashboard/dashboard_writer.py @@ -53,6 +53,7 @@ def _get_write_section() -> Optional[Callable[..., Any]]: from aipass.devpulse.apps.modules import dashboard as _dashboard # type: ignore[import-not-found] _write_section_fn = _dashboard.write_section except ImportError: + logger.warning("[dashboard_writer] devpulse import fallback") _write_section_fn = None return _write_section_fn @@ -74,6 +75,7 @@ def _find_branch_path(branch_name: str) -> Optional[str]: with open(BRANCH_REGISTRY_PATH, "r", encoding="utf-8") as f: registry = json.load(f) except (json.JSONDecodeError, OSError): + logger.warning("[dashboard_writer] Failed to read branch registry") return None for branch in registry.get("branches", []): @@ -213,6 +215,7 @@ def _read_last_checked(branch_path: str) -> str: last_checked = commons.get("last_updated", "") return last_checked if last_checked else epoch except (json.JSONDecodeError, OSError): + logger.warning(f"[dashboard_writer] Failed to read last_checked from dashboard for {branch_path}") return epoch diff --git a/src/commons/apps/handlers/database/db.py b/src/commons/apps/handlers/database/db.py index cd4ba7b4..5bac2052 100644 --- a/src/commons/apps/handlers/database/db.py +++ b/src/commons/apps/handlers/database/db.py @@ -113,6 +113,7 @@ def retry_on_locked(fn: Callable[..., T], *args, **kwargs) -> T: except sqlite3.OperationalError as exc: if "database is locked" not in str(exc): raise + logger.warning(f"[db] Database locked, retrying: {exc}") last_err = exc if delay is None: break @@ -347,6 +348,7 @@ def _register_branches(conn: sqlite3.Connection) -> None: try: registry = json.loads(registry_path.read_text(encoding="utf-8")) except (json.JSONDecodeError, OSError): + logger.warning("[db] Failed to read branch registry JSON") return branches = registry.get("branches", []) diff --git a/src/commons/apps/handlers/digest/digest_ops.py b/src/commons/apps/handlers/digest/digest_ops.py index ed10f10b..2e92b7ff 100644 --- a/src/commons/apps/handlers/digest/digest_ops.py +++ b/src/commons/apps/handlers/digest/digest_ops.py @@ -197,7 +197,7 @@ def show_digest(args: List[str]) -> dict: close_db(conn) except Exception as e: - logger.error(f"Digest query failed: {e}") + logger.error(f"[digest_ops] Digest query failed: {e}") return {"success": False, "error": str(e)} json_handler.log_operation("digest_query", {"top_posts": len(top_posts), "totals": totals}) diff --git a/src/commons/apps/handlers/engagement/engagement_ops.py b/src/commons/apps/handlers/engagement/engagement_ops.py index ab33b2c1..0f3c8f83 100644 --- a/src/commons/apps/handlers/engagement/engagement_ops.py +++ b/src/commons/apps/handlers/engagement/engagement_ops.py @@ -117,7 +117,7 @@ def generate_prompt(args: List[str]) -> dict: } except Exception as e: - logger.error(f"Daily prompt failed: {e}") + logger.error(f"[engagement_ops] Daily prompt creation failed: {e}") return {"success": False, "error": str(e)} @@ -188,5 +188,5 @@ def create_event(args: List[str]) -> dict: } except Exception as e: - logger.error(f"Event creation failed: {e}") + logger.error(f"[engagement_ops] Event creation failed: {e}") return {"success": False, "error": str(e)} diff --git a/src/commons/apps/handlers/feed/feed_ops.py b/src/commons/apps/handlers/feed/feed_ops.py index 72a63146..00b031cd 100644 --- a/src/commons/apps/handlers/feed/feed_ops.py +++ b/src/commons/apps/handlers/feed/feed_ops.py @@ -46,6 +46,7 @@ def format_time_ago(timestamp: str) -> str: else: return timestamp[:10] except (ValueError, TypeError): + logger.warning("[feed_ops] Failed to parse timestamp for relative time") return "unknown" @@ -92,19 +93,19 @@ def display_feed(args: List[str]) -> dict: try: limit = int(args[i + 1]) except ValueError: - pass + logger.warning("[feed_ops] Invalid --limit value, using default") i += 2 elif arg == "--offset" and i + 1 < len(args): try: offset = int(args[i + 1]) except ValueError: - pass + logger.warning("[feed_ops] Invalid --offset value, using default") i += 2 elif arg == "--page" and i + 1 < len(args): try: page = int(args[i + 1]) except ValueError: - pass + logger.warning("[feed_ops] Invalid --page value, using default") i += 2 else: i += 1 diff --git a/src/commons/apps/handlers/identity/identity_ops.py b/src/commons/apps/handlers/identity/identity_ops.py index 8203c208..16326d9f 100644 --- a/src/commons/apps/handlers/identity/identity_ops.py +++ b/src/commons/apps/handlers/identity/identity_ops.py @@ -131,6 +131,7 @@ def get_branch_info_from_registry(branch_path: Path) -> Optional[Dict[str, Any]] return None except Exception: + logger.warning("[identity_ops] Failed to look up branch in registry") return None diff --git a/src/commons/apps/handlers/json/json_handler.py b/src/commons/apps/handlers/json/json_handler.py index d46dd508..4991f088 100644 --- a/src/commons/apps/handlers/json/json_handler.py +++ b/src/commons/apps/handlers/json/json_handler.py @@ -103,6 +103,7 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: if validate_json_structure(data, json_type): return True except (json.JSONDecodeError, OSError): + logger.warning(f"[json_handler] Corrupt or unreadable JSON file: {json_path}") pass template = load_template(json_type, module_name) diff --git a/src/commons/apps/handlers/notifications/dashboard_pipeline.py b/src/commons/apps/handlers/notifications/dashboard_pipeline.py index 7af18c88..14a3c330 100644 --- a/src/commons/apps/handlers/notifications/dashboard_pipeline.py +++ b/src/commons/apps/handlers/notifications/dashboard_pipeline.py @@ -184,7 +184,7 @@ def update_dashboards_for_event( try: update_central() except (OSError, sqlite3.OperationalError): - pass + logger.warning("[dashboard_pipeline] Failed to update central file after event") json_handler.log_operation("dashboard_pipeline", {"event_type": event_type, "dashboards_updated": count}) diff --git a/src/commons/apps/handlers/notifications/notification_ops.py b/src/commons/apps/handlers/notifications/notification_ops.py index b5e531c5..9ee30348 100644 --- a/src/commons/apps/handlers/notifications/notification_ops.py +++ b/src/commons/apps/handlers/notifications/notification_ops.py @@ -109,6 +109,7 @@ def _set_notification_level(args: List[str], level: str) -> dict: try: post_id_int = int(target_id) except ValueError: + logger.warning(f"[notification_ops] Invalid post/thread ID: {target_id}") close_db(conn) return {"success": False, "error": "Post/thread ID must be a number"} row = conn.execute( @@ -135,7 +136,7 @@ def _set_notification_level(args: List[str], level: str) -> dict: return {"success": False, "error": "Failed to set preference"} except Exception as e: - logger.error(f"Notification preference failed: {e}") + logger.error(f"[notification_ops] Notification preference failed: {e}") return {"success": False, "error": str(e)} @@ -166,5 +167,5 @@ def show_preferences(args: List[str]) -> dict: } except Exception as e: - logger.error(f"Preferences query failed: {e}") + logger.error(f"[notification_ops] Preferences query failed: {e}") return {"success": False, "error": str(e)} diff --git a/src/commons/apps/handlers/notifications/preferences.py b/src/commons/apps/handlers/notifications/preferences.py index b485a395..f5bda80e 100644 --- a/src/commons/apps/handlers/notifications/preferences.py +++ b/src/commons/apps/handlers/notifications/preferences.py @@ -79,7 +79,7 @@ def set_preference( json_handler.log_operation("set_preference", {"agent": agent_name, "target_type": target_type, "level": level}) return True except Exception as e: - logger.error(f"Failed to set preference: {e}") + logger.error(f"[preferences] Failed to set preference: {e}") return False diff --git a/src/commons/apps/handlers/posts/post_ops.py b/src/commons/apps/handlers/posts/post_ops.py index e27c50d5..b3218e01 100644 --- a/src/commons/apps/handlers/posts/post_ops.py +++ b/src/commons/apps/handlers/posts/post_ops.py @@ -186,6 +186,7 @@ def view_thread(args: List[str]) -> dict: try: post_id = int(args[0]) except (ValueError, IndexError): + logger.warning(f"[post_ops] Invalid post_id for view_thread: {args[0]!r}") return {"success": False, "error": "Invalid post_id - must be an integer"} conn = None @@ -251,6 +252,7 @@ def delete_post(args: List[str]) -> dict: try: post_id = int(args[0]) except (ValueError, IndexError): + logger.warning(f"[post_ops] Invalid post_id for delete_post: {args[0]!r}") return {"success": False, "error": "Invalid post_id - must be an integer"} # --- Get caller identity --- diff --git a/src/commons/apps/handlers/profiles/profile_ops.py b/src/commons/apps/handlers/profiles/profile_ops.py index 52d0a6df..40d34276 100644 --- a/src/commons/apps/handlers/profiles/profile_ops.py +++ b/src/commons/apps/handlers/profiles/profile_ops.py @@ -80,7 +80,7 @@ def show_profile(args: List[str]) -> dict: return {"success": True, "action": "view", "profile": profile} except Exception as e: - logger.error(f"Profile fetch failed: {e}") + logger.error(f"[profile_ops] Profile fetch failed: {e}") return {"success": False, "error": str(e)} @@ -111,7 +111,7 @@ def _handle_profile_set(args: List[str]) -> dict: return {"success": False, "error": f"Agent '{branch_name}' not found"} except Exception as e: - logger.error(f"Profile update failed: {e}") + logger.error(f"[profile_ops] Profile update failed: {e}") return {"success": False, "error": str(e)} @@ -135,5 +135,5 @@ def list_members(args: List[str]) -> dict: return {"success": True, "agents": agents} except Exception as e: - logger.error(f"Who listing failed: {e}") + logger.error(f"[profile_ops] Member listing failed: {e}") return {"success": False, "error": str(e)} diff --git a/src/commons/apps/handlers/profiles/profile_queries.py b/src/commons/apps/handlers/profiles/profile_queries.py index 0197bcb4..a2e4cc1a 100644 --- a/src/commons/apps/handlers/profiles/profile_queries.py +++ b/src/commons/apps/handlers/profiles/profile_queries.py @@ -17,6 +17,7 @@ import sqlite3 from datetime import datetime, timezone from typing import Optional, Dict, Any, List +from aipass.prax.apps.modules.logger import system_logger as logger from commons.apps.handlers.json import json_handler @@ -209,4 +210,5 @@ def format_time_ago(timestamp: str) -> str: else: return timestamp[:10] except (ValueError, TypeError): + logger.warning("[profile_queries] Failed to parse timestamp for time_ago") return "unknown" diff --git a/src/commons/apps/handlers/rooms/explore_ops.py b/src/commons/apps/handlers/rooms/explore_ops.py index edaa2682..81cc9143 100644 --- a/src/commons/apps/handlers/rooms/explore_ops.py +++ b/src/commons/apps/handlers/rooms/explore_ops.py @@ -82,7 +82,7 @@ def explore_rooms(args: List[str]) -> dict: return result except Exception as e: - logger.error(f"Explore failed: {e}") + logger.error(f"[explore_ops] Explore rooms failed: {e}") return {"success": False, "error": str(e)} @@ -133,5 +133,5 @@ def list_secrets(args: List[str]) -> dict: } except Exception as e: - logger.error(f"Secrets listing failed: {e}") + logger.error(f"[explore_ops] Secrets listing failed: {e}") return {"success": False, "error": str(e)} diff --git a/src/commons/apps/handlers/rooms/room_state_ops.py b/src/commons/apps/handlers/rooms/room_state_ops.py index 4814cd25..d31f8392 100644 --- a/src/commons/apps/handlers/rooms/room_state_ops.py +++ b/src/commons/apps/handlers/rooms/room_state_ops.py @@ -16,6 +16,8 @@ and convenience setters for room personality columns (mood, flavor, entrance). import sqlite3 from typing import Dict, Optional +from aipass.prax.apps.modules.logger import system_logger as logger + from commons.apps.handlers.json import json_handler @@ -37,6 +39,7 @@ def set_room_state(conn: sqlite3.Connection, room_name: str, key: str, value: st json_handler.log_operation("set_room_state", {"room": room_name, "key": key}) return True except Exception: + logger.error(f"[room_state_ops] Failed to set state key '{key}' for room '{room_name}'") return False @@ -49,6 +52,7 @@ def get_room_state(conn: sqlite3.Connection, room_name: str, key: str) -> Option ).fetchone() return row["value"] if row else None except Exception: + logger.error(f"[room_state_ops] Failed to get state key '{key}' for room '{room_name}'") return None @@ -61,6 +65,7 @@ def get_all_room_state(conn: sqlite3.Connection, room_name: str) -> Dict[str, st ).fetchall() return {row["key"]: row["value"] for row in rows} except Exception: + logger.error(f"[room_state_ops] Failed to get all state for room '{room_name}'") return {} @@ -75,6 +80,7 @@ def set_mood(conn: sqlite3.Connection, room_name: str, mood: str) -> bool: conn.commit() return True except Exception: + logger.error(f"[room_state_ops] Failed to set mood for room '{room_name}'") return False @@ -85,6 +91,7 @@ def set_flavor(conn: sqlite3.Connection, room_name: str, text: str) -> bool: conn.commit() return True except Exception: + logger.error(f"[room_state_ops] Failed to set flavor text for room '{room_name}'") return False @@ -95,4 +102,5 @@ def set_entrance(conn: sqlite3.Connection, room_name: str, message: str) -> bool conn.commit() return True except Exception: + logger.error(f"[room_state_ops] Failed to set entrance message for room '{room_name}'") return False diff --git a/src/commons/apps/handlers/rooms/space_ops.py b/src/commons/apps/handlers/rooms/space_ops.py index a897ef43..69fc721b 100644 --- a/src/commons/apps/handlers/rooms/space_ops.py +++ b/src/commons/apps/handlers/rooms/space_ops.py @@ -73,6 +73,7 @@ def get_room_enter_data(room_name: str) -> Dict[str, Any]: json_handler.log_operation("room_enter", {"room": room_name, "post_count": post_count}) except Exception as e: + logger.error(f"[space_ops] Failed to get room enter data for '{room_name}': {e}") result["error"] = str(e) return result @@ -141,6 +142,7 @@ def get_room_look_data(room_name: str) -> Dict[str, Any]: }) except Exception as e: + logger.error(f"[space_ops] Failed to get room look data for '{room_name}': {e}") result["error"] = str(e) return result @@ -176,6 +178,7 @@ def place_decoration(room_name: str, item_name: str, description: str, branch_na result["error"] = "Failed to store decoration" except Exception as e: + logger.error(f"[space_ops] Failed to place decoration '{item_name}' in room '{room_name}': {e}") result["error"] = str(e) return result @@ -237,6 +240,7 @@ def get_visitors_data(room_name: str) -> Dict[str, Any]: result.update({"found": True, "visitors": sorted(visitors)}) except Exception as e: + logger.error(f"[space_ops] Failed to get visitors data for room '{room_name}': {e}") result["error"] = str(e) return result diff --git a/src/commons/apps/handlers/search/search_ops.py b/src/commons/apps/handlers/search/search_ops.py index d7f36fb3..603628a4 100644 --- a/src/commons/apps/handlers/search/search_ops.py +++ b/src/commons/apps/handlers/search/search_ops.py @@ -116,7 +116,7 @@ def run_search(args: List[str]) -> dict: close_db(conn) except Exception as e: - logger.error(f"Search failed: {e}") + logger.error(f"[search_ops] Search query failed: {e}") return {"success": False, "error": str(e)} json_handler.log_operation("search_query", {"query": query, "post_results": len(posts), "comment_results": len(comments_list)}) @@ -153,6 +153,7 @@ def run_log_export(args: List[str]) -> dict: try: limit = int(remaining[idx + 1]) except ValueError: + logger.warning("[search_ops] Invalid --limit value for log export") return {"success": False, "error": "Limit must be a number"} try: @@ -167,7 +168,7 @@ def run_log_export(args: List[str]) -> dict: close_db(conn) except Exception as e: - logger.error(f"Log export failed: {e}") + logger.error(f"[search_ops] Log export failed: {e}") return {"success": False, "error": str(e)} return { diff --git a/src/commons/apps/handlers/social/leaderboard_ops.py b/src/commons/apps/handlers/social/leaderboard_ops.py index 79bd7595..a156e492 100644 --- a/src/commons/apps/handlers/social/leaderboard_ops.py +++ b/src/commons/apps/handlers/social/leaderboard_ops.py @@ -138,5 +138,5 @@ def show_leaderboard(args: List[str]) -> dict: } except Exception as e: - logger.error(f"Leaderboard query failed: {e}") + logger.error(f"[leaderboard_ops] Leaderboard query failed: {e}") return {"success": False, "error": str(e)} diff --git a/src/commons/apps/handlers/welcome/welcome_handler.py b/src/commons/apps/handlers/welcome/welcome_handler.py index 4aee7850..3e28f768 100644 --- a/src/commons/apps/handlers/welcome/welcome_handler.py +++ b/src/commons/apps/handlers/welcome/welcome_handler.py @@ -62,7 +62,7 @@ def create_welcome_post(conn: sqlite3.Connection, branch_name: str) -> Optional[ return post_id except Exception as e: - logger.error(f"Failed to create welcome post for {branch_name}: {e}") + logger.error(f"[welcome_handler] Failed to create welcome post for {branch_name}: {e}") return None diff --git a/src/commons/apps/handlers/welcome/welcome_ops.py b/src/commons/apps/handlers/welcome/welcome_ops.py index 3e75118e..0c00f781 100644 --- a/src/commons/apps/handlers/welcome/welcome_ops.py +++ b/src/commons/apps/handlers/welcome/welcome_ops.py @@ -61,7 +61,7 @@ def run_welcome(args: List[str]) -> dict: return result except Exception as e: - logger.error(f"Welcome command failed: {e}") + logger.error(f"[welcome_ops] Welcome command failed: {e}") if conn: close_db(conn) return {"success": False, "error": str(e)} diff --git a/src/commons/apps/modules/activity.py b/src/commons/apps/modules/activity.py index e2f084fc..8876eca5 100644 --- a/src/commons/apps/modules/activity.py +++ b/src/commons/apps/modules/activity.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[activity] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/artifact.py b/src/commons/apps/modules/artifact.py index 6736e0b0..859b9378 100644 --- a/src/commons/apps/modules/artifact.py +++ b/src/commons/apps/modules/artifact.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[artifact] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/capsule.py b/src/commons/apps/modules/capsule.py index e7e104df..05224031 100644 --- a/src/commons/apps/modules/capsule.py +++ b/src/commons/apps/modules/capsule.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[capsule] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/catchup.py b/src/commons/apps/modules/catchup.py index c315ef7b..127c4923 100644 --- a/src/commons/apps/modules/catchup.py +++ b/src/commons/apps/modules/catchup.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[catchup] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/central.py b/src/commons/apps/modules/central.py index 7ff25d93..90c0cb19 100644 --- a/src/commons/apps/modules/central.py +++ b/src/commons/apps/modules/central.py @@ -23,6 +23,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[central] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/comment.py b/src/commons/apps/modules/comment.py index c7365a94..506b9bf3 100644 --- a/src/commons/apps/modules/comment.py +++ b/src/commons/apps/modules/comment.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[comment] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/commons_identity.py b/src/commons/apps/modules/commons_identity.py index 93afe82a..4f64687f 100644 --- a/src/commons/apps/modules/commons_identity.py +++ b/src/commons/apps/modules/commons_identity.py @@ -25,6 +25,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console, error, warning except ImportError: + logger.warning("[commons_identity] CLI console unavailable, using fallback") from rich.console import Console console = Console() error = lambda msg, **kw: console.print(f"[red]{msg}[/red]") diff --git a/src/commons/apps/modules/database.py b/src/commons/apps/modules/database.py index 4f1d83e7..f3922b07 100644 --- a/src/commons/apps/modules/database.py +++ b/src/commons/apps/modules/database.py @@ -23,6 +23,7 @@ from aipass.prax import logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[database] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/digest.py b/src/commons/apps/modules/digest.py index 42b32c8a..8e61acc4 100644 --- a/src/commons/apps/modules/digest.py +++ b/src/commons/apps/modules/digest.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[digest] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/engagement.py b/src/commons/apps/modules/engagement.py index fb6efa40..078ec4dd 100644 --- a/src/commons/apps/modules/engagement.py +++ b/src/commons/apps/modules/engagement.py @@ -23,6 +23,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[engagement] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/explore.py b/src/commons/apps/modules/explore.py index 1d8a2a82..502b2372 100644 --- a/src/commons/apps/modules/explore.py +++ b/src/commons/apps/modules/explore.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[explore] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/feed.py b/src/commons/apps/modules/feed.py index d2630533..99bdcf27 100644 --- a/src/commons/apps/modules/feed.py +++ b/src/commons/apps/modules/feed.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[feed] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/leaderboard.py b/src/commons/apps/modules/leaderboard.py index e444aff8..fea6e9c4 100644 --- a/src/commons/apps/modules/leaderboard.py +++ b/src/commons/apps/modules/leaderboard.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[leaderboard] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/notification.py b/src/commons/apps/modules/notification.py index be80786f..2e1dbe7e 100644 --- a/src/commons/apps/modules/notification.py +++ b/src/commons/apps/modules/notification.py @@ -23,6 +23,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[notification] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/post.py b/src/commons/apps/modules/post.py index 8e98896f..7bd11a96 100644 --- a/src/commons/apps/modules/post.py +++ b/src/commons/apps/modules/post.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[post] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/profile.py b/src/commons/apps/modules/profile.py index f6442a9f..3b2a1abc 100644 --- a/src/commons/apps/modules/profile.py +++ b/src/commons/apps/modules/profile.py @@ -23,6 +23,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[profile] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/reaction.py b/src/commons/apps/modules/reaction.py index 9091aa39..57726cb2 100644 --- a/src/commons/apps/modules/reaction.py +++ b/src/commons/apps/modules/reaction.py @@ -23,6 +23,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[reaction] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/room.py b/src/commons/apps/modules/room.py index 74dc91d8..49cfccf6 100644 --- a/src/commons/apps/modules/room.py +++ b/src/commons/apps/modules/room.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[room] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/search.py b/src/commons/apps/modules/search.py index 56dff27b..af86e326 100644 --- a/src/commons/apps/modules/search.py +++ b/src/commons/apps/modules/search.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[search] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/space.py b/src/commons/apps/modules/space.py index ea305736..d44ce98f 100644 --- a/src/commons/apps/modules/space.py +++ b/src/commons/apps/modules/space.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console, error except ImportError: + logger.warning("[space] CLI console unavailable, using fallback") from rich.console import Console console = Console() @@ -195,7 +196,7 @@ def _cmd_enter(args: List[str]) -> bool: visitor_name = caller["name"] if caller else "unknown" record_visit(room_name, visitor_name) except Exception: - pass # visit recording is non-critical + logger.warning("[space] Failed to get room state") # visit recording is non-critical return True diff --git a/src/commons/apps/modules/trade.py b/src/commons/apps/modules/trade.py index 1003b8fc..8581188f 100644 --- a/src/commons/apps/modules/trade.py +++ b/src/commons/apps/modules/trade.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[trade] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/commons/apps/modules/welcome.py b/src/commons/apps/modules/welcome.py index 45aab821..081cdf83 100644 --- a/src/commons/apps/modules/welcome.py +++ b/src/commons/apps/modules/welcome.py @@ -22,6 +22,7 @@ from aipass.prax.apps.modules.logger import system_logger as logger try: from aipass.cli.apps.modules import console except ImportError: + logger.warning("[welcome] CLI console unavailable, using fallback") from rich.console import Console console = Console() diff --git a/src/skills/apps/handlers/discovery_handler.py b/src/skills/apps/handlers/discovery_handler.py index 7cd118c1..b4ac446e 100644 --- a/src/skills/apps/handlers/discovery_handler.py +++ b/src/skills/apps/handlers/discovery_handler.py @@ -28,6 +28,7 @@ try: import yaml HAS_YAML = True except ImportError: + logger.warning("yaml package not available — using simple frontmatter parser") HAS_YAML = False @@ -119,6 +120,7 @@ def parse_frontmatter(skill_md_path): try: content = Path(skill_md_path).read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): + logger.warning(f"Failed to read frontmatter from: {skill_md_path}") return None return _extract_frontmatter(content) @@ -153,6 +155,7 @@ def _extract_frontmatter(content): try: return yaml.safe_load(frontmatter_text) except yaml.YAMLError: + logger.warning("YAML parse failed — falling back to simple parser") return _simple_frontmatter_parse(frontmatter_text) else: return _simple_frontmatter_parse(frontmatter_text) @@ -253,7 +256,7 @@ def _parse_simple_value(value): return float(value) return int(value) except ValueError: - pass + logger.warning(f"Could not parse numeric value: {value}") # String (strip quotes) return value.strip("'\"") diff --git a/src/skills/apps/handlers/json/json_handler.py b/src/skills/apps/handlers/json/json_handler.py index 157403b1..ecc8fb94 100644 --- a/src/skills/apps/handlers/json/json_handler.py +++ b/src/skills/apps/handlers/json/json_handler.py @@ -19,6 +19,8 @@ from datetime import datetime from typing import Dict, Any, Optional import inspect +from aipass.prax import logger + # Infrastructure _BRANCH_ROOT = Path(__file__).resolve().parents[3] @@ -49,6 +51,7 @@ def _get_caller_module_name() -> str: return "unknown" except Exception: + logger.warning("Failed to detect caller module name from stack") return "unknown" @@ -70,6 +73,7 @@ def load_template(json_type: str, module_name: str) -> Any: return json.loads(template_str) except Exception: + logger.warning(f"Failed to load JSON template: {json_type} for {module_name}") return None @@ -113,7 +117,7 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: if validate_json_structure(data, json_type): return True except Exception: - pass + logger.warning(f"Corrupt JSON file, will recreate: {json_path}") template = load_template(json_type, module_name) if template is None: @@ -124,6 +128,7 @@ def ensure_json_exists(module_name: str, json_type: str) -> bool: json.dump(template, f, indent=2, ensure_ascii=False) return True except Exception: + logger.error(f"Failed to write JSON file: {json_path}") return False diff --git a/src/skills/apps/handlers/loader_handler.py b/src/skills/apps/handlers/loader_handler.py index 61eafeb5..59e2f4a2 100644 --- a/src/skills/apps/handlers/loader_handler.py +++ b/src/skills/apps/handlers/loader_handler.py @@ -38,6 +38,7 @@ def parse_full_skill_md(skill_md_path): try: content = Path(skill_md_path).read_text(encoding="utf-8") except (OSError, UnicodeDecodeError): + logger.warning(f"Failed to read SKILL.md: {skill_md_path}") return None, None lines = content.strip().splitlines() diff --git a/src/skills/apps/handlers/runner_handler.py b/src/skills/apps/handlers/runner_handler.py index 3f7a95e0..4d86e024 100644 --- a/src/skills/apps/handlers/runner_handler.py +++ b/src/skills/apps/handlers/runner_handler.py @@ -17,6 +17,7 @@ Purpose: orchestration layer to satisfy thin-module standard. """ +from aipass.prax import logger from skills.apps.handlers.json import json_handler @@ -45,6 +46,7 @@ def run_handler(handler, name, action, args, config): "error": None, } except Exception as exc: + logger.error(f"Failed to list actions for {name}: {exc}") return { "success": False, "output": "", @@ -83,6 +85,7 @@ def run_handler(handler, name, action, args, config): "error": None, } except Exception as exc: + logger.error(f"Skill {name} action '{action}' failed: {exc}") return { "success": False, "output": "", diff --git a/src/skills/apps/handlers/template.py b/src/skills/apps/handlers/template.py index b056b8d7..7e202e7d 100644 --- a/src/skills/apps/handlers/template.py +++ b/src/skills/apps/handlers/template.py @@ -9,6 +9,7 @@ import shutil from pathlib import Path +from aipass.prax import logger from skills.apps.handlers.json import json_handler @@ -80,8 +81,7 @@ def copy_template(template_path, target_path, skill_name): content = content.replace("{{SKILL_NAME}}", skill_name) file_path.write_text(content, encoding="utf-8") except UnicodeDecodeError: - # Skip binary files - pass + logger.warning(f"Skipping binary file during template copy: {file_path}") json_handler.log_operation("template_copied", { "template": str(template_path.name), @@ -96,6 +96,7 @@ def copy_template(template_path, target_path, skill_name): } except Exception as e: + logger.error(f"Template copy failed: {e}") # Clean up on failure if target.exists(): shutil.rmtree(str(target)) diff --git a/src/skills/apps/handlers/validator.py b/src/skills/apps/handlers/validator.py index c1f289ef..6a1064e3 100644 --- a/src/skills/apps/handlers/validator.py +++ b/src/skills/apps/handlers/validator.py @@ -10,6 +10,7 @@ import importlib.util import os import shutil +from aipass.prax import logger from skills.apps.handlers.json import json_handler @@ -73,6 +74,7 @@ def _check_pip(packages): if spec is None: missing.append(pkg) except (ModuleNotFoundError, ValueError): + logger.warning(f"Package check failed for: {pkg}") missing.append(pkg) return missing diff --git a/src/skills/apps/modules/creator.py b/src/skills/apps/modules/creator.py index 1c5c6d42..5e017be2 100644 --- a/src/skills/apps/modules/creator.py +++ b/src/skills/apps/modules/creator.py @@ -22,6 +22,7 @@ from skills.apps.handlers.json import json_handler try: from aipass.trigger.apps.modules.core import trigger except ImportError: + logger.warning("trigger module not available — skill events disabled") trigger = None