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:
co-authored by
Claude Opus 4.6
parent
09e759a8a4
commit
babedd9c64
@@ -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)
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user