Files
AIPass/src/commons/apps/handlers/json/json_handler.py
T
AIOSAIandClaude Opus 4.6 babedd9c64 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>
2026-03-10 01:26:42 -07:00

226 lines
6.6 KiB
Python

# =================== AIPass ====================
# Name: json_handler.py
# Description: JSON Auto-Creating Handler
# Version: 1.0.0
# Created: 2026-03-07
# Modified: 2026-03-07
# =============================================
"""
JSON auto-creating handler for The Commons.
Manages per-module JSON files (config, data, log) with template-based
auto-creation, validation, and log rotation.
"""
import json
import os
import inspect
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional
from aipass.prax.apps.modules.logger import system_logger as logger
# Constants - relative path resolution (pip-safe, no hardcoded absolutes)
_HANDLER_DIR = Path(__file__).resolve().parent # .../commons/apps/handlers/json/
_APPS_DIR = _HANDLER_DIR.parent.parent # .../commons/apps/
_COMMONS_ROOT = _APPS_DIR.parent # .../commons/
BRANCH_JSON_DIR = str(_COMMONS_ROOT / "commons_json")
JSON_TEMPLATES_DIR = str(_APPS_DIR / "json_templates")
def _get_caller_module_name() -> str:
"""
Auto-detect calling module name from call stack.
Returns:
Module name (e.g., "imports_standard" from imports_standard.py)
"""
stack = inspect.stack()
if len(stack) > 2:
caller_frame = stack[2]
caller_path = caller_frame.filename
module_name = os.path.splitext(os.path.basename(caller_path))[0]
if module_name and not module_name.startswith("_"):
return module_name
return "unknown"
def load_template(json_type: str, module_name: str) -> Any:
"""Load JSON template from template file."""
template_path = os.path.join(JSON_TEMPLATES_DIR, "default", f"{json_type}.json")
if not os.path.exists(template_path):
raise FileNotFoundError(f"Template not found: {template_path}")
with open(template_path, "r", encoding="utf-8") as f:
template = json.load(f)
template_str = json.dumps(template)
template_str = template_str.replace("{{MODULE_NAME}}", module_name)
template_str = template_str.replace("{{TIMESTAMP}}", datetime.now().date().isoformat())
return json.loads(template_str)
def validate_json_structure(data: Any, json_type: str) -> bool:
"""Validate JSON structure matches expected type."""
if json_type == "config":
if not isinstance(data, dict):
return False
required = ["module_name", "version", "config"]
return all(key in data for key in required)
elif json_type == "data":
if not isinstance(data, dict):
return False
required = ["created", "last_updated"]
return all(key in data for key in required)
elif json_type == "log":
return isinstance(data, list)
return False
def get_json_path(module_name: str, json_type: str) -> str:
"""Get path for module JSON file."""
filename = f"{module_name}_{json_type}.json"
return os.path.join(BRANCH_JSON_DIR, filename)
def ensure_json_exists(module_name: str, json_type: str) -> bool:
"""Ensure JSON file exists, create from template if missing."""
os.makedirs(BRANCH_JSON_DIR, exist_ok=True)
json_path = get_json_path(module_name, json_type)
if os.path.exists(json_path):
try:
with open(json_path, "r", encoding="utf-8") as f:
data = json.load(f)
if validate_json_structure(data, json_type):
return True
except (json.JSONDecodeError, OSError):
pass
template = load_template(json_type, module_name)
with open(json_path, "w", encoding="utf-8") as f:
json.dump(template, f, indent=2, ensure_ascii=False)
return True
def load_json(module_name: str, json_type: str) -> Optional[Any]:
"""Load JSON file, auto-create if missing."""
if not ensure_json_exists(module_name, json_type):
return None
json_path = get_json_path(module_name, json_type)
with open(json_path, "r", encoding="utf-8") as f:
return json.load(f)
def save_json(module_name: str, json_type: str, data: Any) -> bool:
"""Save JSON file."""
json_path = get_json_path(module_name, json_type)
if not validate_json_structure(data, json_type):
raise ValueError(f"Invalid structure for {json_type} JSON")
if json_type == "data" and isinstance(data, dict):
data["last_updated"] = datetime.now().date().isoformat()
with open(json_path, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
def ensure_module_jsons(module_name: str) -> bool:
"""Ensure all 3 JSON files exist for a module."""
ensure_json_exists(module_name, "config")
ensure_json_exists(module_name, "data")
ensure_json_exists(module_name, "log")
return True
def log_operation(
operation: str,
data: Optional[Dict[str, Any]] = None,
module_name: Optional[str] = None,
) -> bool:
"""
Add entry to module log with automatic rotation.
Auto-detects calling module if module_name not provided.
Implements config-controlled log limits to prevent unbounded growth.
Args:
operation: Operation name to log
data: Optional data dict
module_name: Optional module name (auto-detected if not provided)
Returns:
True if successful, False otherwise
"""
if module_name is None:
module_name = _get_caller_module_name()
ensure_module_jsons(module_name)
config = load_json(module_name, "config")
max_entries = 100
if config and "config" in config:
max_entries = config["config"].get("max_log_entries", 100)
log = load_json(module_name, "log")
if log is None:
log = []
entry: Dict[str, Any] = {
"timestamp": datetime.now().isoformat(),
"operation": operation,
}
if data:
entry["data"] = data
log.append(entry)
if len(log) > max_entries:
log = log[-max_entries:]
return save_json(module_name, "log", log)
def increment_counter(module_name: str, counter_name: str, amount: int = 1) -> bool:
"""Increment a counter in data JSON."""
ensure_module_jsons(module_name)
data = load_json(module_name, "data")
if data is None:
return False
if counter_name not in data:
data[counter_name] = 0
data[counter_name] += amount
return save_json(module_name, "data", data)
def update_data_metrics(module_name: str, **metrics: Any) -> bool:
"""Update data metrics."""
ensure_module_jsons(module_name)
data = load_json(module_name, "data")
if data is None:
return False
for key, value in metrics.items():
data[key] = value
return save_json(module_name, "data", data)