diff --git a/.aipass/aipass_global_prompt.md b/.aipass/aipass_global_prompt.md index 7f85f8ab..f68f99e5 100644 --- a/.aipass/aipass_global_prompt.md +++ b/.aipass/aipass_global_prompt.md @@ -57,28 +57,32 @@ drone @prax monitor # Real-time monitoring (interactive) drone @flow create . "Subject" # Create FPLAN (execution plan) drone @flow create . "Subject" master # Create FPLAN master (multi-phase execution) drone @flow create . "Subject" dplan # Create DPLAN (design/planning doc) -drone @flow list # List active plans +drone @flow list open # List active plans ``` **DPLAN** = Design Plan. Thinking, brainstorming, capturing ideas and decisions. Created early — even before you know if you'll build anything. The template explains more when you open it. **FPLAN** = Flow Plan. Building and executing. Default is for single focused tasks. Master is for multi-phase projects that spawn sub-FPLANs per phase. DPLANs come first, FPLANs come when you're ready to build. -## Dispatch — Wake a Branch - -Send a task via email, then wake the branch to process it autonomously. +## Dispatch — Send Task + Wake a Branch ``` -# Step 1: Send the task -drone @ai_mail send @target "Subject" "Body" --dispatch +# One command: send dispatch email + wake target +drone @ai_mail dispatch @target "Subject" "Body" +drone @ai_mail dispatch @target "Subject" "Body" --fresh # Fresh session -# Step 2: Wake the branch +# Just send email (no wake) +drone @ai_mail email @target "Subject" "Body" # FYI, no dispatch header +drone @ai_mail email @target "Subject" "Body" --dispatch # With dispatch header, no wake + +# Wake only (no email) drone @ai_mail dispatch wake @target -drone @ai_mail dispatch wake --fresh @target # Fresh session +drone @ai_mail dispatch wake --fresh @target ``` -- `--dispatch` = recipient must ACT (tasks, bugs, investigations) -- No flag = just informing (FYI, status updates) +- `dispatch @target` = send email with dispatch header + wake (preferred for tasks) +- `email @target` = just mail, no wake (FYI, status updates) +- `--dispatch` flag on `email` = adds dispatch header but doesn't auto-wake **Always reply to dispatch emails.** When devpulse or another branch sends you work, they're waiting for a response. Complete the task, then email back with results. No silent completions — if someone dispatched you, they need to know what happened. @@ -90,13 +94,22 @@ drone @ai_mail dispatch wake --fresh @target # Fresh session **Check seedgo standards.** Before building: `drone @seedgo standards_query aipass_standards` to know what applies. During: check your work against standards as you go. After: `drone @seedgo audit aipass @{branch}` as a final gate before committing. -## Logging +## Logging & Debugging Prax is the ONLY logging system. Every branch uses: ```python from aipass.prax import logger ``` +Two output channels — know the difference: + +- **Console** = what the user sees right now. Command results, errors, success messages. If something fails, the user MUST see it in the console — never fail silently. Use CLI console output for real-time feedback. +- **Prax logs** = what gets written to your `logs/` directory. Operational history for after-the-fact debugging — what resolved, what path was taken, what failed and why. Use `logger.info()`, `logger.warning()`, `logger.error()`. + +**Errors go to BOTH.** Console tells the user something broke. Log tells you (or the next session) what happened and why. + +**Your logs are your first diagnostic tool.** When something unexpected happens — a command fails, output looks wrong, behavior doesn't match — check your `logs/` before trying anything else. The answer is usually already there. Other branches' logs are in their own `logs/` directories — you can read those too if you need to trace cross-branch behavior. Don't write debug scripts, don't add print statements — read your logs. + ## Git Workflow **All PR workflow goes through drone.** Never use raw git commands for commits, branches, or pushes. Drone handles everything atomically with a lockfile that prevents concurrent PR collisions. @@ -118,6 +131,8 @@ drone @git lock # Who has the PR lock? **Never merge.** Only devpulse or Patrick merge PRs. If your PR gets feedback, fix the issues and run `drone @git pr` again. +**Local main is always ahead of origin — that's normal.** `drone @git pr` commits on local main first, then pushes a feature branch for the PR. Your local main will show "ahead of origin" — this is correct. Don't `git pull` to fix it. Patrick merges PRs and pulls when he chooses. Diverged state is expected, not a problem. + ## Context Guardrail If the conversation suddenly shifts to a topic, project, or domain that doesn't relate to your current branch — **say something.** Don't just roll with it. Patrick uses voice input and multiple terminals. He may think he's talking to a different agent. A quick "Hey, this sounds like it's for [other project] — are you in the right terminal?" saves both of you from polluting memories with cross-context noise. Your job is to be the sanity check when the human has 5 windows open. @@ -127,7 +142,11 @@ If the conversation suddenly shifts to a topic, project, or domain that doesn't - **No cross-branch file edits.** If you find an issue in another branch → email them. - **No bare imports.** Always `from aipass.{module}.apps.modules...` - **No hardcoded paths.** Use `Path(__file__).parents[N]` or drone for resolution. -- **No deleting files.** Move to `.archive/` or rename with `(disabled)`. +- **No deleting files.** Tag with `(disabled)` and move to `.archive/`: + - Rename the file: `my_handler.py` → `my_handler(disabled).py`. The `(disabled)` tag is gitignored — it blocks imports and keeps the file out of version control while preserving it locally. + - If `.archive/` doesn't exist in the current directory, create it. Place `.archive/` next to the files being moved — if you're in `handlers/`, the archive goes in `handlers/.archive/`. If in `apps/`, it goes in `apps/.archive/`. + - Move disabled files into `.archive/`. This keeps the working directory clean while preserving everything for recovery. + - Never truly delete files. If something breaks after removal, check `.archive/` first. - **Verify after fixing.** Run a test or command to confirm. Don't say "fixed" until verified. - **Cross-platform.** AIPass is a public package — code must work on Linux, macOS, and Windows. Use `pathlib.Path` not string concatenation. Use `Path.home()` not `~` or `/home/`. Secrets live at `~/.secrets/aipass/` (`Path.home() / ".secrets" / "aipass"`). - **Public repo — no local paths in code.** Never hardcode `/home/username/...` or any machine-specific path. All file paths must derive from `Path(__file__)`, `Path.home()`, or registry lookups. This repo is public — your local directory structure doesn't exist for anyone else. Tests included. @@ -169,14 +188,11 @@ Small knowledge traces that trigger awareness. Not full knowledge — just enoug When adding context to prompts, memories, or docs: plant breadcrumbs, not encyclopedias. Two lines that say "this exists, look here" beat twenty lines explaining how it works. If one source is lost, others reinforce. The system teaches through convention, not search. -**Prompts are signposts, not journals.** Branch prompts (`aipass_local_prompt.md`) are injected every turn — keep them minimal. Never track state, sessions, or current context in prompts. State goes in `.trinity/` and `dev.local.md`. Prompts guide; memories record. +**Prompts are signposts, not journals.** Branch prompts (`aipass_local_prompt.md`) are injected every turn — keep them minimal. Never track state, sessions, or current context in prompts. State goes in `.trinity/` and `STATUS.local.md`. Prompts guide; memories record. ## Claude Code Docs (Local) -Offline mirror of Anthropic's Claude Code docs — no web searches needed. Auto-updates from GitHub. -- `/docs` — list all topics -- `/docs ` — read a doc (e.g. `/docs hooks`, `/docs statusline`, `/docs sub-agents`) -- `/docs whats new` — recent changes +Offline docs: `/docs` to list topics, `/docs ` to read (e.g. `/docs hooks`). ## Docker diff --git a/README.md b/README.md index d48d5554..9d4de259 100644 --- a/README.md +++ b/README.md @@ -159,7 +159,7 @@ All branches follow this structure. Drone resolves `@name` to paths via `AIPASS_ Drone's argument structure is designed so AI agents can operate the entire system through single-line, non-interactive commands. No interactive menus, no prompts, no multi-step wizards. Everything — sending emails, running audits, creating plans, managing backups — is a one-liner: ```bash -drone @ai_mail send @memory "Bug Report" "Search fails without torch" --dispatch +drone @ai_mail dispatch @memory "Bug Report" "Search fails without torch" drone @seedgo audit aipass @memory drone @flow create . "Fix search module" dplan ``` diff --git a/src/aipass/ai_mail/README.md b/src/aipass/ai_mail/README.md index 1fdaa00d..5abb8ce9 100644 --- a/src/aipass/ai_mail/README.md +++ b/src/aipass/ai_mail/README.md @@ -12,12 +12,12 @@ ## Commands / Usage ```bash -drone @ai_mail send @target "Subject" "Body" # Send inter-branch email -drone @ai_mail send @target "Subject" "Body" --dispatch # Send task dispatch email -drone @ai_mail dispatch wake @target # Wake a branch -drone @ai_mail dispatch wake --fresh @target # Fresh wake (no context) -drone @ai_mail inbox # Check inbox -drone @ai_mail --help # Full help +drone @ai_mail dispatch @target "Subject" "Body" # Send dispatch email + wake +drone @ai_mail dispatch @target "Subject" "Body" --fresh # Send + fresh wake +drone @ai_mail email @target "Subject" "Body" # Send email (no wake) +drone @ai_mail dispatch wake @target # Wake only (no email) +drone @ai_mail inbox # Check inbox +drone @ai_mail --help # Full help ``` ## Email Lifecycle diff --git a/src/aipass/ai_mail/apps/ai_mail.py b/src/aipass/ai_mail/apps/ai_mail.py index 24e27148..c4bf8bcc 100644 --- a/src/aipass/ai_mail/apps/ai_mail.py +++ b/src/aipass/ai_mail/apps/ai_mail.py @@ -56,15 +56,16 @@ def print_help(): formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" COMMANDS: + dispatch - Send dispatch email + wake target (one step) + email - Send email to a branch + send - Send email (alias for email) inbox - List emails (new + opened) view - View email content (marks as opened) reply - Reply to email (closes + archives) close - Close email(s) without reply (archives) - send - Send email to a branch sent - View sent messages contacts - Manage contacts ping - Memory health check - dispatch - Dispatch status and log EMAIL LIFECYCLE (v2): new → opened → closed @@ -77,6 +78,14 @@ USAGE: drone @ai_mail --help EXAMPLES: + # Dispatch (send + wake in one command) + drone @ai_mail dispatch @branch "Subject" "Body" + drone @ai_mail dispatch @branch "Subject" "Body" --fresh + + # Send mail (no wake) + drone @ai_mail email @seed "Subject" "Msg" # Send to branch + drone @ai_mail email @all "Subject" "Msg" # Broadcast to all + # Check mail drone @ai_mail inbox # List all emails drone @ai_mail view abc123 # View email (marks as opened) @@ -86,10 +95,6 @@ EXAMPLES: drone @ai_mail close abc123 # Close single email drone @ai_mail close abc123 def456 ghi789 # Close multiple emails drone @ai_mail close all # Close ALL emails - - # Send mail - drone @ai_mail send @seed "Subject" "Msg" # Send to branch - drone @ai_mail send @all "Subject" "Msg" # Broadcast to all """ ) console.print(parser.format_help()) @@ -118,7 +123,7 @@ def print_introspection(): console.print(f" [cyan]•[/cyan] {module_name}") console.print() - console.print("[dim]Run 'python3 ai_mail.py --help' for usage information[/dim]") + console.print("[dim]Run 'drone @ai_mail --help' for usage information[/dim]") console.print() diff --git a/src/aipass/ai_mail/apps/handlers/email/footer.py b/src/aipass/ai_mail/apps/handlers/email/footer.py index 40fd4e64..7ad82d9e 100644 --- a/src/aipass/ai_mail/apps/handlers/email/footer.py +++ b/src/aipass/ai_mail/apps/handlers/email/footer.py @@ -23,7 +23,7 @@ STANDARD_FOOTER = """ □ SEEDGO CHECK → drone @seedgo audit @branch (80%+) □ UPDATE MEMORIES → Your .trinity/local.json records this work □ CLOSE FPLAN → drone @flow close -□ EMAIL SENDER → drone @ai_mail send @ "Subject" "Summary" +□ EMAIL SENDER → drone @ai_mail email @ "Subject" "Summary" Memories = Presence. No update = No learning. ---""" diff --git a/src/aipass/ai_mail/apps/modules/dispatch.py b/src/aipass/ai_mail/apps/modules/dispatch.py index a4864c11..5cde10aa 100644 --- a/src/aipass/ai_mail/apps/modules/dispatch.py +++ b/src/aipass/ai_mail/apps/modules/dispatch.py @@ -33,32 +33,25 @@ def print_help() -> None: Dispatch Module - Agent dispatch management COMMANDS: - dispatch status - Show last 5 dispatch spawns with current status - dispatch daemon - Start the continuous dispatch daemon - dispatch wake - Manually wake a branch (spawn agent without daemon) + dispatch @target "Subject" "Body" - Send dispatch email + wake target + dispatch status - Show last 5 dispatch spawns with current status + dispatch daemon - Start the continuous dispatch daemon + dispatch wake @branch - Wake only (no email sent) -WAKE: - drone wake @branch - Wake branch with default inbox check - drone wake @branch "custom msg" - Wake branch with custom prompt - ai_mail dispatch wake @branch - Same, via ai_mail directly +DISPATCH (send + wake): + drone @ai_mail dispatch @branch "Subject" "Body" # Send + continue wake + drone @ai_mail dispatch @branch "Subject" "Body" --fresh # Send + fresh wake + drone @ai_mail dispatch @branch "Subject" "Body" --no-memory-save + +WAKE ONLY: + drone @ai_mail dispatch wake @branch # Wake with default inbox check + drone @ai_mail dispatch wake @branch "custom" # Wake with custom prompt + drone wake @branch # Shortcut via drone DAEMON: The daemon polls branch inboxes for --dispatch emails and spawns agents. - Run as: ai_mail dispatch daemon - Or standalone: python3 apps/handlers/dispatch/daemon.py - Kill switch: touch /.aipass/autonomous_pause Config: safety_config.json - -EXAMPLE: - ai_mail dispatch status - - DISPATCH STATUS - ──────────────────────────────────── - @flow PID 108957 RUNNING 2m ago - @ai_mail PID 85997 COMPLETED 10m ago - ──────────────────────────────────── - Active: 1 | Total: 2 """ console.print(help_text) @@ -95,6 +88,8 @@ def handle_command(command: str, args: List[str]) -> bool: return _orchestrate_daemon() elif subcommand == "wake": return _orchestrate_wake(args[1:]) + elif subcommand.startswith("@") or subcommand.startswith("/"): + return _orchestrate_dispatch_send(args) else: error(f"Unknown dispatch subcommand: {subcommand}") print_help() @@ -198,6 +193,118 @@ def _orchestrate_wake(args: List[str]) -> bool: return success +def _orchestrate_dispatch_send(args: List[str]) -> bool: + """Orchestrate combined dispatch: send email with --dispatch flag + wake branch.""" + # Parse flags + use_fresh = False + no_memory_save = False + from_branch = None + filtered = [] + i = 0 + while i < len(args): + if args[i] == "--fresh": + use_fresh = True + i += 1 + continue + if args[i] == "--no-memory-save": + no_memory_save = True + i += 1 + continue + if args[i] == "--from" and i + 1 < len(args): + from_branch = args[i + 1] + i += 2 + continue + filtered.append(args[i]) + i += 1 + + if len(filtered) < 3: + error("Usage: dispatch @target \"Subject\" \"Body\" [--fresh] [--no-memory-save]") + return True + + target = filtered[0] + subject = filtered[1] + body = filtered[2] + + logger.info(f"[dispatch] Combined dispatch: send + wake for {target}") + json_handler.log_operation("dispatch_send_and_wake", { + "target": target, "subject": subject, "fresh": use_fresh + }) + + # --- Step 1: Send dispatch email --- + console.print(f"\nSending dispatch email to {target}...") + + from aipass.ai_mail.apps.handlers.email.send import resolve_sender_info, send_to_single + from aipass.ai_mail.apps.handlers.email.create import create_email_file, load_email_file + from aipass.ai_mail.apps.handlers.email.delivery import deliver_email_to_branch + from aipass.ai_mail.apps.handlers.email.header import prepend_dispatch_header + from aipass.ai_mail.apps.handlers.email.error_dispatch import dispatch_send_error, on_email_delivered + from aipass.ai_mail.apps.handlers.email.dashboard_sync import push_dashboard_update + from aipass.ai_mail.apps.handlers.users.user import get_current_user + from aipass.ai_mail.apps.handlers.registry.read import get_branch_by_email + + try: + from aipass.ai_mail.apps.handlers.central_writer import update_central + except ImportError: + update_central = None + + _ai_mail_dir = Path(__file__).resolve().parents[2] + _repo_root = _ai_mail_dir.parents[2] + + def _delivery_callback(branch_path, new_count, opened_count, total): + on_email_delivered(branch_path, new_count, opened_count, total, + push_dashboard_fn=push_dashboard_update, + update_central_fn=update_central) + + try: + user_info = resolve_sender_info( + from_branch, _repo_root, _ai_mail_dir, get_branch_by_email, get_current_user + ) + message = prepend_dispatch_header(body, no_memory_save=no_memory_save) + + send_ok, send_error = send_to_single( + target, subject, message, user_info, True, no_memory_save, + None, target, create_email_file, load_email_file, + deliver_email_to_branch, _delivery_callback, + json_handler.log_operation, update_central + ) + + if not send_ok: + error(f"Send failed: {send_error}") + dispatch_send_error(target, subject, send_error or "", deliver_email_to_branch) + return False + + console.print(f"[green]Email sent to {target}[/green]") + + try: + from aipass.trigger.apps.modules.core import trigger + trigger.fire('email_dispatched', to=target, subject=subject) + except Exception: + pass + + except Exception as e: + logger.error(f"[dispatch] Send phase failed: {e}") + error(f"Send failed: {e}") + return False + + # --- Step 2: Wake the branch --- + console.print(f"\nWaking {target}...") + + from aipass.ai_mail.apps.handlers.dispatch.wake import wake_branch + dispatch_status, wake_ok = wake_branch( + target, fresh=use_fresh, + sender=user_info.get("email_address", "@ai_mail") + ) + console.print(dispatch_status.format()) + + if not wake_ok: + console.print( + f"[yellow]Email sent but wake failed — retry: " + f"drone @ai_mail dispatch wake {target}[/yellow]" + ) + + return True + + def _orchestrate_daemon() -> bool: """Orchestrate daemon startup.""" logger.info("[dispatch] Starting dispatch daemon") @@ -212,7 +319,7 @@ def print_introspection(): """Display module introspection info.""" console.print() console.print("dispatch Module") - console.print("Orchestrates dispatch commands: status tracking, daemon management, and manual branch wake.") + console.print("Orchestrates dispatch commands: combined send+wake, status tracking, daemon management, and manual wake.") console.print() console.print("Connected Handlers:") console.print(" handlers/dispatch/") @@ -221,6 +328,11 @@ def print_introspection(): console.print(" - status.py (calculate_age — calculate age string from timestamp)") console.print(" - wake.py (wake_branch — manually wake a branch by spawning an agent)") console.print(" - daemon.py (run_daemon — start the continuous dispatch daemon)") + console.print(" handlers/email/ (used by combined dispatch)") + console.print(" - send.py (resolve_sender_info, send_to_single — send email pipeline)") + console.print(" - create.py (create_email_file, load_email_file — email file creation)") + console.print(" - delivery.py (deliver_email_to_branch — inbox delivery)") + console.print(" - header.py (prepend_dispatch_header — dispatch header injection)") console.print() diff --git a/src/aipass/ai_mail/apps/modules/email.py b/src/aipass/ai_mail/apps/modules/email.py index 088c0bff..33c5b2b5 100644 --- a/src/aipass/ai_mail/apps/modules/email.py +++ b/src/aipass/ai_mail/apps/modules/email.py @@ -71,7 +71,8 @@ HELP_TEXT = """ Email Module - Send and manage branch-to-branch email (Lifecycle v2) COMMANDS: - send - Send email to a branch + email - Send email to a branch (preferred) + send - Send email to a branch (alias for email) inbox - View inbox messages (new + opened) view - View email content and mark as opened reply - Reply to email (auto-closes original) @@ -80,12 +81,16 @@ COMMANDS: contacts - Manage contacts USAGE: - ai_mail send @recipient "subject" "message" [--dispatch] [--reply-to @branch] + ai_mail email @recipient "subject" "message" [--dispatch] [--reply-to @branch] + ai_mail send @recipient "subject" "message" (same as email) ai_mail inbox | view | reply "msg" | close | sent | contacts FLAGS: - --dispatch Spawn agent at target branch to execute + --dispatch Mark as dispatch task (adds dispatch header) --reply-to Redirect replies to a different branch + --no-memory-save Skip memory update requirement in dispatch header + +NOTE: To send + wake in one step, use: drone @ai_mail dispatch @target "Subject" "Body" """ @@ -99,7 +104,7 @@ def handle_command(command: str, args: List[str]) -> bool: if command in ("--help", "-h"): print_help() return True - valid = ["send", "inbox", "view", "close", "reply", "sent", "contacts", "read"] + valid = ["send", "email", "inbox", "view", "close", "reply", "sent", "contacts", "read"] if command not in valid: return False if args and args[0] in ['--help', '-h', 'help']: @@ -107,7 +112,8 @@ def handle_command(command: str, args: List[str]) -> bool: return True dispatch = { - "send": handle_send, "inbox": handle_inbox, "view": handle_view, + "send": handle_send, "email": handle_send, + "inbox": handle_inbox, "view": handle_view, "close": handle_close, "reply": handle_reply, "read": handle_view, "sent": handle_sent, "contacts": handle_contacts, } diff --git a/src/aipass/backup/.gitignore b/src/aipass/backup/.gitignore index 9cf1dfc4..b7066f9d 100644 --- a/src/aipass/backup/.gitignore +++ b/src/aipass/backup/.gitignore @@ -12,3 +12,6 @@ build/ *.log *.tmp *.swp + +processed_plans + diff --git a/src/aipass/devpulse/.aipass/aipass_local_prompt.md b/src/aipass/devpulse/.aipass/aipass_local_prompt.md index 884d908c..5642a7b6 100644 --- a/src/aipass/devpulse/.aipass/aipass_local_prompt.md +++ b/src/aipass/devpulse/.aipass/aipass_local_prompt.md @@ -32,11 +32,11 @@ When a task belongs to a specialist, send it there. Don't burn context doing the ## Key Commands ``` -drone @ai_mail send @target "Subject" "Body" --dispatch # Task email -drone @ai_mail dispatch wake @target # Wake branch +drone @ai_mail dispatch @target "Subject" "Body" # Send + wake (one command) +drone @ai_mail email @target "Subject" "Body" # Just mail, no wake drone @flow create . "Subject" # Create FPLAN drone @flow create . "Subject" dplan # Create DPLAN (dplan template) -drone @flow list # Active plans +drone @flow list open # Active plans drone systems # All branches ``` diff --git a/src/aipass/prax/apps/handlers/monitoring/branch_detector.py b/src/aipass/prax/apps/handlers/monitoring/branch_detector.py index 57c0029f..a4a3fd83 100644 --- a/src/aipass/prax/apps/handlers/monitoring/branch_detector.py +++ b/src/aipass/prax/apps/handlers/monitoring/branch_detector.py @@ -20,13 +20,11 @@ Uses BRANCH_REGISTRY.json for accurate mapping with caching for performance. from pathlib import Path from typing import Optional, Dict, Set import json -import logging -from functools import lru_cache -from aipass.prax import logger +from aipass.prax.apps.modules.logger import get_direct_logger from aipass.prax.apps.handlers.json import json_handler -# logger imported from aipass.prax +logger = get_direct_logger() class BranchDetector: diff --git a/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py b/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py index 04eba676..27870619 100644 --- a/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py +++ b/src/aipass/prax/apps/handlers/monitoring/file_watcher_integration.py @@ -37,9 +37,9 @@ from pathlib import Path from typing import List, Tuple, Optional, Any from datetime import datetime -from aipass.prax import logger +from aipass.prax.apps.modules.logger import get_direct_logger -# logger imported from aipass.prax +logger = get_direct_logger() # ============================================================================= # IMPORTS - File watcher and event queue diff --git a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py index d0e263d8..f6edade0 100644 --- a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py +++ b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py @@ -24,7 +24,9 @@ from typing import Optional, Dict from watchdog.events import FileSystemEventHandler -from aipass.prax import logger +from aipass.prax.apps.modules.logger import get_direct_logger + +logger = get_direct_logger() # Trigger integration (graceful fallback if unavailable) try: diff --git a/src/aipass/prax/apps/handlers/monitoring/log_watcher.py b/src/aipass/prax/apps/handlers/monitoring/log_watcher.py index ecc24e03..3f47cabb 100644 --- a/src/aipass/prax/apps/handlers/monitoring/log_watcher.py +++ b/src/aipass/prax/apps/handlers/monitoring/log_watcher.py @@ -27,7 +27,7 @@ from datetime import datetime from typing import Optional, Dict, Any import re -from aipass.prax import logger +from aipass.prax.apps.modules.logger import get_direct_logger from watchdog.observers import Observer as WatchdogObserver from watchdog.events import FileSystemEventHandler @@ -48,8 +48,7 @@ except ImportError: from aipass.prax.apps.handlers.json import json_handler -# Logger -# logger imported from aipass.prax +logger = get_direct_logger() def _generate_error_hash(module_name: str, message: str) -> str: diff --git a/src/aipass/prax/apps/modules/monitor.py b/src/aipass/prax/apps/modules/monitor.py index 6b4a3d28..98df5469 100755 --- a/src/aipass/prax/apps/modules/monitor.py +++ b/src/aipass/prax/apps/modules/monitor.py @@ -411,7 +411,7 @@ def _file_watcher_worker(): _event_queue.enqueue(MonitoringEvent( priority=2, event_type='log', branch='PRAX', action='warning', level='warning', timestamp=datetime.now(), - message="File watcher: inotify unavailable — using polling fallback (slower)" + message="File watcher: inotify watch limit reached (VSCode/editors consume most of the 65K default). Using polling fallback (slower). Fix: sudo sysctl -w fs.inotify.max_user_watches=524288" )) # Fallback to PollingObserver @@ -458,7 +458,7 @@ def _log_watcher_worker(): _event_queue.enqueue(MonitoringEvent( priority=2, event_type='log', branch='PRAX', action='warning', level='warning', timestamp=datetime.now(), - message="Log watcher: inotify unavailable — using polling fallback (slower)" + message="Log watcher: inotify watch limit reached (VSCode/editors consume most of the 65K default). Using polling fallback (slower). Fix: sudo sysctl -w fs.inotify.max_user_watches=524288" )) try: start_log_watcher(_event_queue, use_polling=True) diff --git a/src/aipass/seedgo/apps/handlers/audit/branch_audit.py b/src/aipass/seedgo/apps/handlers/audit/branch_audit.py index 08c667e5..5bd95b28 100644 --- a/src/aipass/seedgo/apps/handlers/audit/branch_audit.py +++ b/src/aipass/seedgo/apps/handlers/audit/branch_audit.py @@ -59,14 +59,16 @@ def _run_all_files(checker, name: str, files: List[Dict], bypass_rules: list) -> if checks and not any(w in c.get("message", "").lower() for c in checks for w in ("skipped", "not applicable")): scores.append(score) - if not r.get("passed", True): - failed = [c for c in checks if not c.get("passed", False)] - if failed: - msgs = [c.get("message", "Unknown") for c in failed] - v = {"file": fi["name"], "path": fi["file"], "score": score, "issues": msgs} - if name == "modules": - v["message"] = "; ".join(msgs) - violations.append(v) + # Collect violations from ANY file with failing checks, regardless of + # overall pass/fail. The old gate (not r["passed"]) hid violations + # from files scoring 75-99% — score dropped but nothing was reported. + failed = [c for c in checks if not c.get("passed", False)] + if failed: + msgs = [c.get("message", "Unknown") for c in failed] + v = {"file": fi["name"], "path": fi["file"], "score": score, "issues": msgs} + if name == "modules": + v["message"] = "; ".join(msgs) + violations.append(v) return violations, scores def _log_structure_post_checks(branch_path: Path) -> tuple: diff --git a/src/aipass/trigger/apps/handlers/events/error_detected.py b/src/aipass/trigger/apps/handlers/events/error_detected.py index 1ad243a9..9ce5c345 100644 --- a/src/aipass/trigger/apps/handlers/events/error_detected.py +++ b/src/aipass/trigger/apps/handlers/events/error_detected.py @@ -317,7 +317,7 @@ SEED STANDARDS REMINDER: - Fixes scoring below 80% on Seed audit should NOT be shipped - clean up first REPORT TO @devpulse: - ai_mail send @devpulse "ERROR {error_hash} - [STATUS]" "Findings..." + ai_mail email @devpulse "ERROR {error_hash} - [STATUS]" "Findings..." Include: Error ID, severity (low/medium/high/critical), what you found, action taken or recommended. """ diff --git a/src/aipass/trigger/apps/handlers/events/error_logged.py b/src/aipass/trigger/apps/handlers/events/error_logged.py index 97bea020..34ce0a53 100644 --- a/src/aipass/trigger/apps/handlers/events/error_logged.py +++ b/src/aipass/trigger/apps/handlers/events/error_logged.py @@ -206,7 +206,7 @@ DECISION TREE: -> STOP immediately, escalate to @devpulse with full context REPORT TO @devpulse: - ai_mail send @devpulse "ERROR {error_hash[:8]} - [STATUS]" "Findings..." + ai_mail email @devpulse "ERROR {error_hash[:8]} - [STATUS]" "Findings..." """ diff --git a/src/commons/apps/handlers/database/db.py b/src/commons/apps/handlers/database/db.py index b18208ea..ef9443f9 100644 --- a/src/commons/apps/handlers/database/db.py +++ b/src/commons/apps/handlers/database/db.py @@ -14,8 +14,8 @@ and schema bootstrapping for The Commons social network. Pure sqlite3 stdlib - no external dependencies. -Database location: {AIPASS_ROOT}/.aipass/commons.db -where AIPASS_ROOT comes from environment variable or defaults to ~/.aipass/ +Database location: {project_root}/.aipass/commons.db +resolved by walking up from __file__ to find project root. """ import os @@ -32,22 +32,49 @@ from commons.apps.handlers.json import json_handler # DATABASE PATHS # ============================================================================= +def _find_project_root() -> Optional[Path]: + """ + Walk up from this file to find the project root. + + Looks for AIPASS_REGISTRY.json as the project root marker. + This file only exists at the true project root, unlike .aipass/ + which exists at both branch and project levels. + + Returns: + Path to project root, or None if not found. + """ + current = Path(__file__).resolve().parent + for _ in range(10): + if (current / "AIPASS_REGISTRY.json").exists(): + return current + parent = current.parent + if parent == current: + break + current = parent + return None + + def _get_db_path() -> Path: """ Resolve the database file path. - Uses AIPASS_ROOT environment variable if set, otherwise defaults - to ~/.aipass/. Database stored at {root}/.aipass/commons.db. + Resolution order: + 1. Walk up from __file__ to find project root → {root}/.aipass/commons.db + 2. AIPASS_ROOT environment variable → {AIPASS_ROOT}/.aipass/commons.db + 3. Fallback → ~/.aipass/commons.db Returns: Path to the commons.db file. """ + project_root = _find_project_root() + if project_root: + return project_root / ".aipass" / "commons.db" + aipass_root = os.environ.get("AIPASS_ROOT", "") if aipass_root: - root = Path(aipass_root) / ".aipass" - else: - root = Path.home() / ".aipass" - return root / "commons.db" + return Path(aipass_root) / ".aipass" / "commons.db" + + return Path.home() / ".aipass" / "commons.db" DB_PATH = _get_db_path()