feat(system): dispatch UX, prompt updates, prax monitor, ref sweep

- ai_mail: dispatch UX redesign (send+wake one command), email alias, --fresh flag
- Global prompt: dispatch syntax, flow list open, logging section
- Devpulse prompt: updated dispatch/email/list commands
- Root README: updated drone example
- Prax: monitor fixes (branch_detector, file_watcher, filesystem_handler, log_watcher)
- Trigger: send→email ref updates
- Seedgo: branch_audit updates
- Commons: database handler updates
- Backup: gitignore additions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-03-18 23:32:43 -07:00
co-authored by Claude Opus 4.6
parent e30f54b355
commit 696136e271
18 changed files with 261 additions and 91 deletions
+33 -17
View File
@@ -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 <topic>` — read a doc (e.g. `/docs hooks`, `/docs statusline`, `/docs sub-agents`)
- `/docs whats new` — recent changes
Offline docs: `/docs` to list topics, `/docs <topic>` to read (e.g. `/docs hooks`).
## Docker
+1 -1
View File
@@ -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
```
+6 -6
View File
@@ -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
+12 -7
View File
@@ -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()
@@ -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 <plan_id>
□ EMAIL SENDER → drone @ai_mail send @<sender> "Subject" "Summary"
□ EMAIL SENDER → drone @ai_mail email @<sender> "Subject" "Summary"
Memories = Presence. No update = No learning.
---"""
+133 -21
View File
@@ -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 <repo_root>/.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()
+11 -5
View File
@@ -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 <id> | reply <id> "msg" | close <id> | 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,
}
+3
View File
@@ -12,3 +12,6 @@ build/
*.log
*.tmp
*.swp
processed_plans
@@ -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
```
@@ -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:
@@ -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
@@ -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:
@@ -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:
+2 -2
View File
@@ -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)
@@ -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:
@@ -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.
"""
@@ -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..."
"""
+35 -8
View File
@@ -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()