feat(system): seedgo v2 operational, full system audit, 14/15 branches at 99%

Three days of intensive work bringing seedgo to full operational status
and driving all branches through comprehensive standards compliance.

Seedgo v2.0.0:
- 22 checkers active (up from 20), standards pack fully operational
- New introspection standard researched from Dev-Pass, FPLAN-0017 open
- Bypass system for false positives (.seedgo config)
- Standards query and audit commands fully functional

System-wide audit (FPLAN-0016):
- All 14 auditable branches at 99%+ compliance
- CLI imports standardized across all branches (console from cli.apps.modules)
- handle_command(command, args) → bool contract added to all modules
- print_help() function naming fixed for checker pattern matching
- Handler extraction: large modules split, file I/O moved to handler layer
- New handlers created across ai_mail, backup, daemon, flow, skills, spawn, seedgo

Branch-specific highlights:
- ai_mail: email.py split 840→420 lines, 4 new handlers
- flow: dplan_flow.py 688→591 lines, 4 new handlers
- seedgo: massive restructure — standards moved to handlers/aipass_standards/,
  old standards/ tree removed, bypass system added, diagnostics module
- commons: database module added, CLI imports fixed
- skills: 5 handle_commands added, help function renamed
- trigger: error reporter handler, handle_command routing
- All branches: consistent architecture, clean drone routing

Culture doc (CLAUDE.md) added — documents AIPass philosophy, identity,
memory system, and collaboration principles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-03-10 01:26:42 -07:00
co-authored by Claude Opus 4.6
parent 09e759a8a4
commit babedd9c64
589 changed files with 17140 additions and 24881 deletions
+26 -23
View File
@@ -1,20 +1,9 @@
# ===================AIPASS====================
# META DATA HEADER
# Name: actions.py - Action Registry CLI Module
# Date: 2026-03-02
# =================== AIPass ====================
# Name: actions.py
# Description: Action Registry CLI Module
# Version: 1.0.0
# Category: daemon/modules
#
# CHANGELOG (Max 5 entries):
# - v1.0.0 (2026-03-02): Initial creation - DPLAN-043
# * List, toggle, info, set reminder, set schedule
# * Plugin migration command
# * Formatted Rich console output
#
# CODE STANDARDS:
# - Seed pattern compliance - console.print() for all output
# - Thin orchestration - handlers implement logic
# Created: 2026-03-02
# Modified: 2026-03-02
# =============================================
"""
@@ -29,11 +18,9 @@ import sys
from pathlib import Path
from typing import List
import logging
logger = logging.getLogger(__name__)
from aipass.prax import logger
from rich.console import Console
console = Console()
from aipass.cli.apps.modules import console
def _header(text):
console.print(f"\n[bold cyan]{'='*70}[/bold cyan]")
@@ -63,6 +50,22 @@ from aipass.daemon.apps.handlers.actions.actions_registry import (
MODULE_NAME = "actions"
# =============================================
# INTROSPECTION
# =============================================
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("actions Module")
console.print("CLI interface for the numbered action registry (DPLAN-043)")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/actions/")
console.print(" - actions_registry.py (list_actions, get_action, toggle_action, delete_action, create_action, migrate_plugins, next_due_str — registry CRUD)")
console.print()
# =============================================
# OUTPUT FORMATTING
# =============================================
@@ -169,7 +172,7 @@ def _print_action_detail(action: dict) -> None:
console.print()
def _print_help() -> None:
def print_help() -> None:
"""Display help using Rich formatted output."""
console.print()
_header("Actions -- Numbered Action Registry")
@@ -455,7 +458,7 @@ def handle_command(command: str, args: List[str]) -> bool:
try:
# No args or help
if not args or args[0] in ['--help', '-h', 'help']:
_print_help()
print_help()
return True
subcommand = args[0]
@@ -517,7 +520,7 @@ def main() -> None:
args = sys.argv[1:]
if not args or args[0] in ['--help', '-h', 'help']:
_print_help()
print_help()
return
handle_command('actions', args)
+96 -548
View File
@@ -1,19 +1,9 @@
# ===================AIPASS====================
# META DATA HEADER
# Name: activity_report.py - Branch Activity Report Generator Module
# Date: 2026-01-30
# Version: 0.1.0
# Category: daemon/modules
#
# CHANGELOG (Max 5 entries):
# - v0.1.0 (2026-01-30): Initial implementation - FPLAN-0266 Phase 3
#
# CODE STANDARDS:
# - Module orchestrates handlers - handlers implement logic
# - Plain text CLI output (no Rich colors for this module)
# - Type hints on all functions
# - Standard library only + handler imports
# =================== AIPass ====================
# Name: activity_report.py
# Description: Branch Activity Report Generator Module
# Version: 0.2.0
# Created: 2026-01-30
# Modified: 2026-03-08
# =============================================
"""
@@ -28,16 +18,19 @@ This is a MODULE (orchestration layer) that coordinates:
- red_flag_detector: Detects presence violations (code changed but memory not updated)
"""
from datetime import datetime, timedelta
from typing import Dict, Any, List, Optional
from typing import Dict, Any, List
import logging
logger = logging.getLogger(__name__)
from aipass.prax import logger
# logger imported from aipass.prax
# Import monitoring handlers using absolute imports
from aipass.daemon.apps.handlers.monitoring import activity_collector
from aipass.daemon.apps.handlers.monitoring import memory_health
from aipass.daemon.apps.handlers.monitoring import red_flag_detector
from aipass.cli.apps.modules import console
# Import report generation handler (implementation lives in handler layer)
from aipass.daemon.apps.handlers.monitoring.report_generator import (
generate_activity_report,
generate_branch_report,
get_json_report,
)
# =============================================
@@ -46,466 +39,21 @@ from aipass.daemon.apps.handlers.monitoring import red_flag_detector
MODULE_NAME = "activity_report"
# Status symbols for CLI output
SYMBOL_OK = "[OK]"
SYMBOL_WARNING = "[!]"
SYMBOL_RED = "[X]"
SYMBOL_INACTIVE = "[-]"
# =============================================
# HELPER FUNCTIONS
# INTROSPECTION
# =============================================
def _format_time_ago(iso_timestamp: Optional[str]) -> str:
"""
Format an ISO timestamp as human-readable time ago.
Args:
iso_timestamp: ISO format timestamp string or None.
Returns:
Human-readable string like "2h ago" or "3d ago".
"""
if not iso_timestamp:
return "never"
try:
dt = datetime.fromisoformat(iso_timestamp)
delta = datetime.now() - dt
hours = delta.total_seconds() / 3600
if hours < 1:
minutes = int(delta.total_seconds() / 60)
return f"{minutes}m ago"
elif hours < 24:
return f"{int(hours)}h ago"
else:
days = int(hours / 24)
return f"{days}d ago"
except (ValueError, TypeError):
return "unknown"
def _get_status_symbol(status: str) -> str:
"""
Get CLI symbol for a status code.
Args:
status: Status string (OK, WARNING, RED, NO_ACTIVITY, RED_FLAG, ERROR).
Returns:
ASCII symbol for display.
"""
status_upper = status.upper()
if status_upper in ("OK",):
return SYMBOL_OK
elif status_upper in ("WARNING",):
return SYMBOL_WARNING
elif status_upper in ("RED", "RED_FLAG", "ERROR"):
return SYMBOL_RED
else:
return SYMBOL_INACTIVE
def _box_header(title: str, width: int = 50) -> str:
"""Generate a boxed header line."""
return "=" * width + "\n" + title + "\n" + "=" * width
def _section_header(title: str) -> str:
"""Generate a section header line."""
return f"\n{title}\n" + "-" * len(title)
# =============================================
# DATA AGGREGATION
# =============================================
def _aggregate_data(since_hours: float = 24) -> Dict[str, Any]:
"""
Aggregate data from all monitoring handlers.
Args:
since_hours: Time window in hours to analyze.
Returns:
Dict with combined data from all handlers.
"""
since_timestamp = datetime.now() - timedelta(hours=since_hours)
# Get red flag summary (includes all branch statuses)
red_flag_summary = red_flag_detector.get_red_flag_summary(since_timestamp)
# Get activity data
activity_data = activity_collector.get_all_branch_activity(since_timestamp)
# Get memory health for each branch
branch_paths = activity_collector.get_branch_paths()
memory_health_data = {}
for branch_info in branch_paths:
name = branch_info.get("name", "")
path = branch_info.get("path", "")
if name and path:
health = memory_health.get_memory_health_status(path, name)
memory_health_data[name] = health
return {
"timestamp": datetime.now().isoformat(),
"time_window_hours": since_hours,
"red_flag_summary": red_flag_summary,
"activity_data": activity_data,
"memory_health": memory_health_data,
}
# =============================================
# REPORT GENERATION
# =============================================
def generate_activity_report(
since_hours: float = 24,
verbosity: str = "normal"
) -> str:
"""
Generate a formatted CLI-ready activity report.
Aggregates data from all monitoring handlers and produces a
human-readable report with branch status, red flags, and recommendations.
Args:
since_hours: Time window in hours to analyze (default: 24).
verbosity: Report detail level - "brief", "normal", or "detailed".
Returns:
Formatted string report suitable for CLI display.
"""
data = _aggregate_data(since_hours)
red_flags = data["red_flag_summary"]
activity = data["activity_data"]
health_data = data["memory_health"]
lines: List[str] = []
# Header
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
lines.append(_box_header(f"BRANCH ACTIVITY REPORT - {timestamp}\nTime window: Last {since_hours:.0f} hours"))
# Summary section
total_branches = red_flags.get("total_branches", 0)
active_branches = red_flags.get("ok", 0)
red_flag_count = red_flags.get("red_flags", 0)
no_activity_count = red_flags.get("no_activity", 0)
error_count = red_flags.get("errors", 0)
# Count memory health statuses
health_ok = sum(1 for h in health_data.values() if h.get("overall_status") == "OK")
health_warning = sum(1 for h in health_data.values() if h.get("overall_status") == "WARNING")
health_red = sum(1 for h in health_data.values() if h.get("overall_status") == "RED")
lines.append(_section_header("SUMMARY"))
lines.append(f"Active branches: {active_branches}/{total_branches}")
lines.append(f"RED FLAGS: {red_flag_count}")
lines.append(f"Memory health: {health_ok} OK, {health_warning} warning, {health_red} red")
if error_count > 0:
lines.append(f"Scan errors: {error_count}")
# Red Flags section
lines.append(_section_header("RED FLAGS (requires attention)"))
violations = red_flags.get("violations", [])
if violations:
for v in violations:
branch = v.get("branch_name", "UNKNOWN")
reason = v.get("reason", "Unknown reason")
code_count = v.get("code_change_count", 0)
lines.append(f" {SYMBOL_RED} {branch}")
lines.append(f" Reason: {reason}")
lines.append(f" Code changes: {code_count} files")
else:
lines.append(" [None - all active branches updated memories]")
# Branch Status section
lines.append(_section_header("BRANCH STATUS"))
all_branches = red_flags.get("all_branches", [])
if verbosity == "brief":
# Just counts, no individual branches
pass
else:
for branch_status in all_branches:
name = branch_status.get("branch_name", "UNKNOWN")
status = branch_status.get("status", "UNKNOWN")
reason = branch_status.get("reason", "")
# Get memory health for this branch
branch_health = health_data.get(name, {})
mem_status = branch_health.get("overall_status", "UNKNOWN")
symbol = _get_status_symbol(status)
# Format status line
if status == "NO_ACTIVITY":
lines.append(f" {SYMBOL_INACTIVE} {name} - inactive (no changes)")
elif status == "RED_FLAG":
lines.append(f" {SYMBOL_RED} {name} - RED FLAG: {reason}")
elif status == "ERROR":
lines.append(f" {SYMBOL_RED} {name} - ERROR: {reason}")
else:
# Get memory update time
mem_update = branch_status.get("memory_last_update")
time_ago = _format_time_ago(mem_update)
mem_symbol = _get_status_symbol(mem_status)
if mem_status == "OK":
lines.append(f" {SYMBOL_OK} {name} - OK (memory updated {time_ago})")
else:
lines.append(f" {SYMBOL_WARNING} {name} - {mem_status} (memory updated {time_ago})")
# Detailed mode: show file changes
if verbosity == "detailed" and status != "NO_ACTIVITY":
code_changes = branch_status.get("code_changes", [])
memory_modified = branch_status.get("memory_files_modified", [])
if code_changes:
lines.append(f" Code files: {len(code_changes)}")
for cf in code_changes[:5]:
lines.append(f" - {cf.get('file', 'unknown')}")
if len(code_changes) > 5:
lines.append(f" ... and {len(code_changes) - 5} more")
if memory_modified:
lines.append(f" Memory files: {len(memory_modified)}")
for mf in memory_modified[:3]:
lines.append(f" - {mf.get('file', 'unknown')}")
# Recommendations section
lines.append(_section_header("RECOMMENDATIONS"))
recommendations: List[str] = []
# Add recommendations for red flags
for v in violations:
branch = v.get("branch_name", "UNKNOWN")
recommendations.append(f"- {branch}: Update memory files to reflect code changes")
# Add recommendations for warning health status
for name, health in health_data.items():
if health.get("overall_status") == "WARNING":
issues = health.get("issues", [])
if issues:
recommendations.append(f"- {name}: {issues[0]}")
elif health.get("overall_status") == "RED":
issues = health.get("issues", [])
if issues:
recommendations.append(f"- {name}: URGENT - {issues[0]}")
if recommendations:
for rec in recommendations[:10]: # Limit to 10 recommendations
lines.append(f" {rec}")
if len(recommendations) > 10:
lines.append(f" ... and {len(recommendations) - 10} more")
else:
lines.append(" [None - system healthy]")
lines.append("") # Trailing newline
return "\n".join(lines)
def generate_branch_report(
branch_name: str,
since_hours: float = 24
) -> str:
"""
Generate a detailed report for a single branch.
Provides a deep dive on one branch including all file changes,
memory status, and specific recommendations.
Args:
branch_name: Name of the branch (uppercase, e.g., "DRONE").
since_hours: Time window in hours to analyze.
Returns:
Formatted string report for the specified branch.
"""
since_timestamp = datetime.now() - timedelta(hours=since_hours)
# Find the branch path
branch_paths = activity_collector.get_branch_paths()
branch_path = None
for bp in branch_paths:
if bp.get("name", "").upper() == branch_name.upper():
branch_path = bp.get("path")
branch_name = bp.get("name", branch_name) # Use canonical name
break
if not branch_path:
return f"ERROR: Branch '{branch_name}' not found in registry."
lines: List[str] = []
# Header
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
lines.append(_box_header(f"BRANCH REPORT: {branch_name}\n{timestamp} | Last {since_hours:.0f} hours"))
# Get branch data
red_flag_status = red_flag_detector.get_branch_status(
branch_name, branch_path, since_timestamp
)
health_status = memory_health.get_memory_health_status(branch_path, branch_name)
activity_data = activity_collector.scan_branch_activity(
branch_name, branch_path, since_timestamp
)
# Status Overview
lines.append(_section_header("STATUS OVERVIEW"))
rf_status = red_flag_status.get("status", "UNKNOWN")
rf_reason = red_flag_status.get("reason", "")
mem_status = health_status.get("overall_status", "UNKNOWN")
symbol = _get_status_symbol(rf_status)
lines.append(f"Activity Status: {symbol} {rf_status}")
lines.append(f" Reason: {rf_reason}")
mem_symbol = _get_status_symbol(mem_status)
lines.append(f"Memory Health: {mem_symbol} {mem_status}")
lines.append(f"Path: {branch_path}")
# File Changes
lines.append(_section_header("FILE CHANGES"))
code_files = activity_data.get("code_files", [])
memory_files = activity_data.get("memory_files", [])
lines.append(f"Code files modified: {len(code_files)}")
for cf in code_files:
mtime = _format_time_ago(cf.get("mtime"))
lines.append(f" - {cf.get('name', 'unknown')} ({mtime})")
lines.append(f"\nMemory files modified: {len(memory_files)}")
for mf in memory_files:
mtime = _format_time_ago(mf.get("mtime"))
lines.append(f" - {mf.get('name', 'unknown')} ({mtime})")
if not code_files and not memory_files:
lines.append(" [No files modified in time window]")
# Memory Health Details
lines.append(_section_header("MEMORY HEALTH DETAILS"))
file_check = health_status.get("file_check", {})
lines.append("Required files:")
for fname, exists in file_check.get("required", {}).items():
symbol = SYMBOL_OK if exists else SYMBOL_RED
lines.append(f" {symbol} {fname}")
lines.append("\nOptional files:")
for fname, exists in file_check.get("optional", {}).items():
symbol = SYMBOL_OK if exists else SYMBOL_WARNING
lines.append(f" {symbol} {fname}")
# Freshness
freshness = health_status.get("freshness_checks", {})
if freshness:
lines.append("\nFreshness:")
for fname, fresh in freshness.items():
status = fresh.get("status", "UNKNOWN")
message = fresh.get("message", "")
symbol = _get_status_symbol(status)
lines.append(f" {symbol} {fname}: {message}")
# Issues
issues = health_status.get("issues", [])
if issues:
lines.append(_section_header("ISSUES"))
for issue in issues:
lines.append(f" - {issue}")
# Recommendations
lines.append(_section_header("RECOMMENDATIONS"))
recommendations: List[str] = []
if rf_status == "RED_FLAG":
recommendations.append("Update memory files to document recent code changes")
for issue in issues:
if "Missing required" in issue:
recommendations.append(f"Create missing file: {issue.split(':')[-1].strip()}")
elif "stale" in issue.lower() or "not modified" in issue.lower():
recommendations.append("Review and update memory files")
if recommendations:
for rec in recommendations:
lines.append(f" - {rec}")
else:
lines.append(" [None - branch is healthy]")
lines.append("")
return "\n".join(lines)
def get_json_report(since_hours: float = 24) -> Dict[str, Any]:
"""
Get raw report data as a dictionary for programmatic access.
Useful for storing report snapshots, API responses, or further processing.
Args:
since_hours: Time window in hours to analyze.
Returns:
Dict containing all aggregated data from handlers.
"""
data = _aggregate_data(since_hours)
red_flags = data["red_flag_summary"]
health_data = data["memory_health"]
activity = data["activity_data"]
# Build summary
health_ok = sum(1 for h in health_data.values() if h.get("overall_status") == "OK")
health_warning = sum(1 for h in health_data.values() if h.get("overall_status") == "WARNING")
health_red = sum(1 for h in health_data.values() if h.get("overall_status") == "RED")
summary = {
"total_branches": red_flags.get("total_branches", 0),
"active_branches": red_flags.get("ok", 0),
"red_flags": red_flags.get("red_flags", 0),
"no_activity": red_flags.get("no_activity", 0),
"errors": red_flags.get("errors", 0),
"health_ok": health_ok,
"health_warning": health_warning,
"health_red": health_red,
}
# Build per-branch data
branches_data: Dict[str, Any] = {}
all_branches = red_flags.get("all_branches", [])
for branch_status in all_branches:
name = branch_status.get("branch_name", "")
if not name:
continue
branches_data[name] = {
"red_flag_status": branch_status,
"memory_health": health_data.get(name, {}),
"activity": activity.get("branches", {}).get(name, {}),
}
return {
"timestamp": data["timestamp"],
"time_window_hours": data["time_window_hours"],
"summary": summary,
"violations": red_flags.get("violations", []),
"branches": branches_data,
}
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("activity_report Module")
console.print("Branch activity report generator — monitors file changes, memory health, and presence violations")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/monitoring/")
console.print(" - report_generator.py (generate_activity_report, generate_branch_report, get_json_report — report generation and JSON output)")
console.print()
# =============================================
@@ -514,71 +62,71 @@ def get_json_report(since_hours: float = 24) -> Dict[str, Any]:
def _print_activity_help() -> None:
"""Display help for the activity command."""
print()
print("=" * 60)
print("ACTIVITY - Quick Activity Summary")
print("=" * 60)
print()
print("USAGE:")
print(" drone @daemon activity")
print(" daemon activity")
print(" daemon activity --hours 48")
print()
print("DESCRIPTION:")
print(" Quick 24-hour activity summary (default).")
print(" Shows branch status, red flags, and recommendations.")
print()
print("OPTIONS:")
print(" --hours N, -t N Time window in hours (default: 24)")
print(" --help, -h Show this help message")
print()
console.print()
console.print("=" * 60)
console.print("ACTIVITY - Quick Activity Summary")
console.print("=" * 60)
console.print()
console.print("USAGE:")
console.print(" drone @daemon activity")
console.print(" daemon activity")
console.print(" daemon activity --hours 48")
console.print()
console.print("DESCRIPTION:")
console.print(" Quick 24-hour activity summary (default).")
console.print(" Shows branch status, red flags, and recommendations.")
console.print()
console.print("OPTIONS:")
console.print(" --hours N, -t N Time window in hours (default: 24)")
console.print(" --help, -h Show this help message")
console.print()
def _print_activity_report_help() -> None:
"""Display help for the activity-report command."""
print()
print("=" * 60)
print("ACTIVITY-REPORT - Full Detailed Report")
print("=" * 60)
print()
print("USAGE:")
print(" drone @daemon activity-report")
print(" daemon activity-report")
print(" daemon activity-report --hours 48")
print(" daemon activity-report --json")
print()
print("DESCRIPTION:")
print(" Full detailed activity report with file-level changes.")
print(" Includes per-branch breakdown and complete recommendations.")
print()
print("OPTIONS:")
print(" --hours N, -t N Time window in hours (default: 24)")
print(" --json, -j Output raw JSON data")
print(" --help, -h Show this help message")
print()
console.print()
console.print("=" * 60)
console.print("ACTIVITY-REPORT - Full Detailed Report")
console.print("=" * 60)
console.print()
console.print("USAGE:")
console.print(" drone @daemon activity-report")
console.print(" daemon activity-report")
console.print(" daemon activity-report --hours 48")
console.print(" daemon activity-report --json")
console.print()
console.print("DESCRIPTION:")
console.print(" Full detailed activity report with file-level changes.")
console.print(" Includes per-branch breakdown and complete recommendations.")
console.print()
console.print("OPTIONS:")
console.print(" --hours N, -t N Time window in hours (default: 24)")
console.print(" --json, -j Output raw JSON data")
console.print(" --help, -h Show this help message")
console.print()
def _print_branch_health_help() -> None:
"""Display help for the branch-health command."""
print()
print("=" * 60)
print("BRANCH-HEALTH - Single Branch Deep Dive")
print("=" * 60)
print()
print("USAGE:")
print(" drone @daemon branch-health DRONE")
print(" daemon branch-health FLOW")
print(" daemon branch-health SEED --hours 48")
print()
print("DESCRIPTION:")
print(" Deep dive report for a single branch.")
print(" Shows all file changes, memory health, and specific recommendations.")
print()
print("OPTIONS:")
print(" <branch_name> Required - branch name (e.g., DRONE, FLOW, SEED)")
print(" --hours N, -t N Time window in hours (default: 24)")
print(" --help, -h Show this help message")
print()
console.print()
console.print("=" * 60)
console.print("BRANCH-HEALTH - Single Branch Deep Dive")
console.print("=" * 60)
console.print()
console.print("USAGE:")
console.print(" drone @daemon branch-health DRONE")
console.print(" daemon branch-health FLOW")
console.print(" daemon branch-health SEED --hours 48")
console.print()
console.print("DESCRIPTION:")
console.print(" Deep dive report for a single branch.")
console.print(" Shows all file changes, memory health, and specific recommendations.")
console.print()
console.print("OPTIONS:")
console.print(" <branch_name> Required - branch name (e.g., DRONE, FLOW, SEED)")
console.print(" --hours N, -t N Time window in hours (default: 24)")
console.print(" --help, -h Show this help message")
console.print()
def _parse_hours_arg(args: List[str]) -> float:
@@ -631,7 +179,7 @@ def handle_command(command: str, args: List[str]) -> bool:
hours = _parse_hours_arg(args)
report = generate_activity_report(since_hours=hours, verbosity="normal")
print(report)
console.print(report)
return True
# Handle 'activity-report' command - detailed report
@@ -646,10 +194,10 @@ def handle_command(command: str, args: List[str]) -> bool:
if '--json' in args or '-j' in args:
import json
data = get_json_report(hours)
print(json.dumps(data, indent=2))
console.print(json.dumps(data, indent=2))
else:
report = generate_activity_report(since_hours=hours, verbosity="detailed")
print(report)
console.print(report)
return True
# Handle 'branch-health' command - single branch report
@@ -674,15 +222,15 @@ def handle_command(command: str, args: List[str]) -> bool:
i += 1
if not branch_name:
print("ERROR: branch-health requires a branch name")
print()
print("Usage: branch-health <branch_name> [--hours N]")
print("Example: branch-health DRONE")
console.print("ERROR: branch-health requires a branch name")
console.print()
console.print("Usage: branch-health <branch_name> [--hours N]")
console.print("Example: branch-health DRONE")
return True
hours = _parse_hours_arg(args)
report = generate_branch_report(branch_name, since_hours=hours)
print(report)
console.print(report)
return True
# Not our command
@@ -729,13 +277,13 @@ def main() -> None:
if args.json:
import json
data = get_json_report(args.hours)
print(json.dumps(data, indent=2))
console.print(json.dumps(data, indent=2))
elif args.branch:
report = generate_branch_report(args.branch, args.hours)
print(report)
console.print(report)
else:
report = generate_activity_report(args.hours, args.verbosity)
print(report)
console.print(report)
if __name__ == "__main__":
+52 -85
View File
@@ -1,18 +1,9 @@
# ===================AIPASS====================
# META DATA HEADER
# Name: schedule.py - DAEMON Scheduled Follow-ups Module
# Date: 2026-02-04
# =================== AIPass ====================
# Name: schedule.py
# Description: DAEMON Scheduled Follow-ups Module
# Version: 1.0.0
# Category: daemon/modules
#
# CHANGELOG (Max 5 entries):
# - v1.0.0 (2026-02-04): Initial implementation - fire-and-forget scheduled tasks
#
# CODE STANDARDS:
# - Seed pattern compliance - console.print() for all output
# - Thin orchestration - handlers implement logic
# - Type hints on all functions
# Created: 2026-02-04
# Modified: 2026-02-04
# =============================================
"""
@@ -28,11 +19,9 @@ import argparse
from pathlib import Path
from typing import List
import logging
logger = logging.getLogger(__name__)
from aipass.prax import logger
from rich.console import Console
console = Console()
from aipass.cli.apps.modules import console
def _header(text):
console.print(f"\n[bold cyan]{'='*70}[/bold cyan]")
@@ -48,7 +37,8 @@ def _error(text):
# Handler imports
from aipass.daemon.apps.handlers.schedule.task_registry import (
load_tasks, create_task, delete_task, get_due_tasks, mark_completed, parse_due_date,
mark_dispatching, mark_pending, recover_stale_dispatches
mark_dispatching, mark_pending, recover_stale_dispatches, process_due_tasks_batch,
ensure_lock_dir,
)
# File lock for single-instance execution
@@ -72,6 +62,22 @@ except ImportError:
MODULE_NAME = "schedule"
# =============================================
# INTROSPECTION
# =============================================
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("schedule Module")
console.print("CLI interface for fire-and-forget scheduled follow-ups")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/schedule/")
console.print(" - task_registry.py (load_tasks, create_task, delete_task, get_due_tasks, mark_completed, parse_due_date, mark_dispatching, mark_pending, recover_stale_dispatches, process_due_tasks_batch, ensure_lock_dir — task CRUD and processing)")
console.print()
_DAEMON_ROOT = Path(__file__).resolve().parents[3] # src/aipass/daemon/
JSON_DIR = _DAEMON_ROOT / "daemon_json"
@@ -247,7 +253,7 @@ def _handle_run_due(_args: List[str]) -> bool:
return _process_due_tasks()
lock_file = JSON_DIR / "schedule.lock"
lock_file.parent.mkdir(parents=True, exist_ok=True)
ensure_lock_dir()
# Try to acquire lock (non-blocking)
lock = FileLock(lock_file, timeout=0)
@@ -260,86 +266,47 @@ def _handle_run_due(_args: List[str]) -> bool:
def _process_due_tasks() -> bool:
"""Process due tasks sequentially with status tracking."""
import time
"""Process due tasks -- delegates to handler, formats output."""
try:
# Recover any stale dispatches (stuck > 5 minutes)
recovered = recover_stale_dispatches(max_age_minutes=5)
if recovered:
console.print(f"[dim]Recovered {recovered} stale dispatch(es)[/dim]")
# Delegate to handler for all implementation logic
email_fn = send_email_direct if AI_MAIL_AVAILABLE else None
results = process_due_tasks_batch(send_email_fn=email_fn, stale_max_age=5)
due_tasks = get_due_tasks()
# Display results (module responsibility)
if results["recovered"]:
console.print(f"[dim]Recovered {results['recovered']} stale dispatch(es)[/dim]")
if not due_tasks:
if results["due"] == 0:
console.print("[dim]No tasks due at this time.[/dim]")
return True
console.print()
_header(f"Running {len(due_tasks)} Due Task(s)")
_header(f"Running {results['due']} Due Task(s)")
console.print()
success_count = 0
fail_count = 0
for task_result in results.get("processed_tasks", []):
task_id = task_result.get("id", "")[:8]
recipient = task_result.get("recipient", "")
task_desc = task_result.get("task", "")[:40]
status = task_result.get("status", "")
for task in due_tasks:
task_id = task.get("id", "")
recipient = task.get("recipient", "")
task_desc = task.get("task", "")
message = task.get("message", "")
# Mark as dispatching (prevents re-dispatch)
mark_dispatching(task_id)
# Build email body
email_body = f"{task_desc}"
if message:
email_body += f"\n\nDetails:\n{message}"
# Send the email
if not AI_MAIL_AVAILABLE:
mark_pending(task_id)
if status == "sent":
_success(f"Sent to {recipient}: {task_desc}")
logger.info(f"[DAEMON] Scheduled email sent: {task_id} -> {recipient}")
elif status == "skipped":
_error(f"ai_mail not available, cannot send to {recipient}")
fail_count += 1
continue
try:
email_sent = send_email_direct(
to_branch=recipient,
subject=f"[SCHEDULED] {task_desc}",
message=email_body,
from_branch='@daemon',
auto_execute=True,
reply_to='@dev_central'
)
if email_sent:
mark_completed(task_id)
_success(f"Sent to {recipient}: {task_desc[:40]}")
success_count += 1
logger.info(f"[DAEMON] Scheduled email sent: {task_id[:8]} -> {recipient}")
else:
# Reset to pending for retry
mark_pending(task_id)
_error(f"Failed to send to {recipient}: {task_desc[:40]}")
fail_count += 1
logger.error(f"[DAEMON] Scheduled email failed: {task_id[:8]} -> {recipient}")
except Exception as e:
# Reset to pending for retry
mark_pending(task_id)
_error(f"Error sending to {recipient}: {e}")
fail_count += 1
logger.error(f"[DAEMON] Scheduled email error: {task_id[:8]} -> {recipient}: {e}")
# Small delay between dispatches (prevents thundering herd)
time.sleep(1.0)
elif status == "failed":
_error(f"Failed to send to {recipient}: {task_desc}")
logger.error(f"[DAEMON] Scheduled email failed: {task_id} -> {recipient}")
elif status == "error":
_error(f"Error sending to {recipient}: {task_result.get('error', '')}")
logger.error(f"[DAEMON] Scheduled email error: {task_id} -> {recipient}: {task_result.get('error', '')}")
console.print()
console.print(f"[bold]Results:[/bold] {success_count} sent, {fail_count} failed")
console.print(f"[bold]Results:[/bold] {results['success']} sent, {results['failed']} failed")
console.print()
return fail_count == 0
return results["failed"] == 0
except Exception as e:
_error(f"Failed to run due tasks: {e}")
@@ -0,0 +1,134 @@
# =================== AIPass ====================
# Name: scheduler_ops.py
# Description: Scheduler Cron Operations Module
# Version: 1.0.0
# Created: 2026-03-08
# Modified: 2026-03-08
# =============================================
"""
Scheduler operations module -- facade for cron entry point.
Provides a clean module-layer interface over handler functions
used by scheduler_cron.py. Entry-level scripts import from
this module instead of reaching into handlers directly.
"""
from aipass.prax import logger
try:
from aipass.cli.apps.modules.display import console
except ImportError:
from rich.console import Console
console = Console()
# =============================================
# TELEGRAM NOTIFICATIONS
# =============================================
try:
from aipass.daemon.apps.handlers.schedule.telegram_notifier import (
notify_triggered,
notify_complete,
notify_error,
)
TELEGRAM_AVAILABLE = True
except ImportError:
TELEGRAM_AVAILABLE = False
notify_triggered = None
notify_complete = None
notify_error = None
# =============================================
# TASK REGISTRY
# =============================================
try:
from aipass.daemon.apps.handlers.schedule.task_registry import (
get_due_tasks,
mark_dispatching,
mark_completed,
mark_pending,
recover_stale_dispatches,
)
TASK_REGISTRY_AVAILABLE = True
except ImportError:
TASK_REGISTRY_AVAILABLE = False
get_due_tasks = None
mark_dispatching = None
mark_completed = None
mark_pending = None
recover_stale_dispatches = None
# =============================================
# ACTION REGISTRY (DPLAN-043)
# =============================================
try:
from aipass.daemon.apps.handlers.actions.actions_registry import (
load_registry,
is_action_due,
update_last_run,
mark_reminder_completed,
migrate_plugins,
next_due_str,
)
ACTION_REGISTRY_AVAILABLE = True
except ImportError:
ACTION_REGISTRY_AVAILABLE = False
load_registry = None
is_action_due = None
update_last_run = None
mark_reminder_completed = None
migrate_plugins = None
next_due_str = None
# =============================================
# INTROSPECTION
# =============================================
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("scheduler_ops Module")
console.print("Facade for scheduler_cron.py — re-exports handler functions for cron entry point")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/schedule/")
console.print(" - telegram_notifier.py (notify_triggered, notify_complete, notify_error — Telegram notifications)")
console.print(" - task_registry.py (get_due_tasks, mark_dispatching, mark_completed, mark_pending, recover_stale_dispatches — task lifecycle)")
console.print()
console.print(" handlers/actions/")
console.print(" - actions_registry.py (load_registry, is_action_due, update_last_run, mark_reminder_completed, migrate_plugins, next_due_str — action registry)")
console.print()
# =============================================
# DRONE ROUTING
# =============================================
def handle_command(command: str, args: list) -> bool:
"""Handle commands routed by the entry point.
Args:
command: Command name
args: Additional arguments
Returns:
True if command was handled, False otherwise
"""
if command == "scheduler-ops":
from aipass.cli.apps.modules import console
console.print()
console.print("[bold cyan]Scheduler Ops[/bold cyan] - Cron operations facade")
console.print()
console.print(f" [dim]Telegram available:[/dim] {TELEGRAM_AVAILABLE}")
console.print(f" [dim]Task registry:[/dim] {TASK_REGISTRY_AVAILABLE}")
console.print(f" [dim]Action registry:[/dim] {ACTION_REGISTRY_AVAILABLE}")
console.print()
console.print("[dim]This module is a facade used by scheduler_cron.py.[/dim]")
console.print()
return True
return False
+26 -21
View File
@@ -1,18 +1,9 @@
# ===================AIPASS====================
# META DATA HEADER
# Name: update.py - DAEMON Status Digest Module
# Date: 2026-01-29
# =================== AIPass ====================
# Name: update.py
# Description: DAEMON Status Digest Module
# Version: 1.0.0
# Category: daemon/modules
#
# CHANGELOG (Max 5 entries):
# - v1.0.0 (2026-01-29): Initial implementation - digest of DAEMON activity
#
# CODE STANDARDS:
# - Seed pattern compliance - console.print() for all output
# - Thin orchestration - handlers implement logic
# - Type hints on all functions
# Created: 2026-01-29
# Modified: 2026-01-29
# =============================================
"""
@@ -26,11 +17,9 @@ Returns digest of DAEMON activity for check-ins.
import sys
from typing import Dict, Any, List
import logging
logger = logging.getLogger(__name__)
from aipass.prax import logger
from rich.console import Console
console = Console()
from aipass.cli.apps.modules import console
def _header(text):
console.print(f"\n[bold cyan]{'='*70}[/bold cyan]")
@@ -52,6 +41,22 @@ from aipass.daemon.apps.handlers.update.data_loader import (
MODULE_NAME = "update"
# =============================================
# INTROSPECTION
# =============================================
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("update Module")
console.print("Returns digest of DAEMON activity for check-ins")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/update/")
console.print(" - data_loader.py (load_inbox, load_local, categorize_messages, get_session_summary, get_escalations — inbox and session data)")
console.print()
# =============================================
# OUTPUT FORMATTING
# =============================================
@@ -104,7 +109,7 @@ def _print_digest(inbox_data: Dict[str, Any], local_data: Dict[str, Any]) -> Non
console.print()
def _print_help() -> None:
def print_help() -> None:
"""Display help using Rich formatted output."""
console.print()
_header("Update Module - DAEMON Status Digest")
@@ -146,7 +151,7 @@ def handle_command(command: str, args: list) -> bool:
try:
if args and args[0] in ['--help', '-h', 'help']:
_print_help()
print_help()
return True
inbox_data = load_inbox()
@@ -171,7 +176,7 @@ def main() -> None:
args = sys.argv[1:]
if len(args) == 0 or args[0] in ['--help', '-h', 'help']:
_print_help()
print_help()
return
handle_command('update', args)
@@ -0,0 +1,84 @@
# =================== AIPass ====================
# Name: wakeup_ops.py
# Description: Wake-Up Cron Operations Module
# Version: 1.0.0
# Created: 2026-03-08
# Modified: 2026-03-08
# =============================================
"""
Wake-up operations module -- facade for cron entry point.
Provides a clean module-layer interface over handler functions
used by daemon_wakeup.py. Entry-level scripts import from
this module instead of reaching into handlers directly.
"""
from aipass.prax import logger
try:
from aipass.cli.apps.modules.display import console
except ImportError:
from rich.console import Console
console = Console()
# =============================================
# DAEMON BOT TELEGRAM NOTIFICATIONS
# =============================================
try:
from aipass.daemon.apps.handlers.schedule.assistant_notifier import (
notify_wakeup,
notify_report,
notify_error,
)
TELEGRAM_AVAILABLE = True
except ImportError:
TELEGRAM_AVAILABLE = False
notify_wakeup = None
notify_report = None
notify_error = None
# =============================================
# INTROSPECTION
# =============================================
def print_introspection():
"""Display module introspection info."""
console.print()
console.print("wakeup_ops Module")
console.print("Facade for daemon_wakeup.py — re-exports daemon bot Telegram notifications")
console.print()
console.print("Connected Handlers:")
console.print(" handlers/schedule/")
console.print(" - assistant_notifier.py (notify_wakeup, notify_report, notify_error — daemon bot Telegram notifications)")
console.print()
# =============================================
# DRONE ROUTING
# =============================================
def handle_command(command: str, args: list) -> bool:
"""Handle commands routed by the entry point.
Args:
command: Command name
args: Additional arguments
Returns:
True if command was handled, False otherwise
"""
if command == "wakeup-ops":
from aipass.cli.apps.modules import console
console.print()
console.print("[bold cyan]Wakeup Ops[/bold cyan] - Cron wake-up facade")
console.print()
console.print(f" [dim]Telegram available:[/dim] {TELEGRAM_AVAILABLE}")
console.print()
console.print("[dim]This module is a facade used by daemon_wakeup.py.[/dim]")
console.print()
return True
return False