feat(seedgo): standards overhaul — meta AUDIT_SCOPE, JSON structure redesign, log two-tier
- meta_check.py: add AUDIT_SCOPE = "all_files" — catches 8 old-format headers in memory (100% → 75%)
- json_structure_check.py: rewrite with code wiring validation (import + log_operation check for modules AND handlers), remove 100% fallthrough
- json_structure_content.py: show code pattern as standard, not JSON templates
- json_handler.py: auto-detect branch name from Path(__file__), write to {branch}_json/ instead of apps/aipass_json/
- log_structure_check.py: update to two-tier model (branch-root logs/ only)
- log_structure_content.py: document simplified two-tier logging
DPLAN-0056: Three-JSON Pattern Redesign (decisions locked)
Co-Authored-By: @seedgo <seedgo@aipass>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
@seedgo
Claude Opus 4.6
parent
79b09e8c1d
commit
0a1b53f57d
@@ -1,26 +1,34 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: json_structure_check.py
|
||||
# Description: JSON Structure Standards Checker Handler
|
||||
# Version: 2.0.0
|
||||
# Version: 3.0.0
|
||||
# Created: 2026-03-05
|
||||
# Modified: 2026-03-05
|
||||
# Modified: 2026-03-17
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
JSON Structure Standards Checker Handler
|
||||
|
||||
Validates JSON handling patterns for pip packages:
|
||||
- json_handler.py uses relative paths (not hardcoded absolute)
|
||||
- Modules use json_handler for JSON operations (not direct json.load/dump)
|
||||
- Branch detection via AIPASS_REGISTRY.json and BRANCH_REGISTRY.json
|
||||
Validates three-JSON code wiring in modules and handlers.
|
||||
|
||||
For every .py file in apps/modules/ and apps/handlers/:
|
||||
1. Must import json_handler from the branch's handlers/json package
|
||||
2. Must call json_handler.log_operation() at least once
|
||||
|
||||
For json_handler.py itself (in a json/ directory):
|
||||
- Validates handler config (relative paths, no hardcoded absolutes)
|
||||
|
||||
Entry points and other files outside modules/handlers are skipped.
|
||||
"""
|
||||
|
||||
import re
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Audit scope: scan every .py file, not just entry point
|
||||
AUDIT_SCOPE = "all_files"
|
||||
|
||||
|
||||
def is_bypassed(file_path: str, standard: str, line: int | None = None, bypass_rules: list | None = None) -> bool:
|
||||
"""Check if a violation should be bypassed"""
|
||||
@@ -45,14 +53,29 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
|
||||
"""
|
||||
Check if module follows JSON structure standards.
|
||||
|
||||
For pip packages, validates:
|
||||
1. json_handler.py uses relative paths (Path(__file__).resolve().parent)
|
||||
2. json_handler.py does NOT use hardcoded absolute paths (BRANCH_ROOT, Path.home())
|
||||
3. Modules use json_handler instead of direct json.load/dump
|
||||
Routing:
|
||||
a. json_handler.py in a json/ dir -> validate handler config
|
||||
b. File in apps/modules/ -> check code wiring
|
||||
c. File in apps/handlers/ -> check code wiring
|
||||
d. Everything else (entry points) -> skip (not applicable)
|
||||
|
||||
Args:
|
||||
module_path: Path to Python module to check
|
||||
bypass_rules: Optional bypass rules
|
||||
|
||||
Returns:
|
||||
dict with passed, checks, score, standard keys
|
||||
"""
|
||||
checks = []
|
||||
path = Path(module_path)
|
||||
|
||||
if is_bypassed(module_path, 'json_structure', bypass_rules=bypass_rules):
|
||||
return {
|
||||
'passed': True,
|
||||
'checks': [{'name': 'Bypassed', 'passed': True, 'message': 'Standard bypassed via .seedgo/bypass.json'}],
|
||||
'score': 100,
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
if not path.exists():
|
||||
return {
|
||||
'passed': False,
|
||||
@@ -61,9 +84,17 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
# Skip __init__.py files
|
||||
if path.name == '__init__.py':
|
||||
return {
|
||||
'passed': True,
|
||||
'checks': [{'name': 'JSON structure check', 'passed': True, 'message': '__init__.py file (skipped)'}],
|
||||
'score': 100,
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
content = path.read_text(encoding='utf-8')
|
||||
except Exception as e:
|
||||
return {
|
||||
'passed': False,
|
||||
@@ -72,36 +103,166 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
if 'json_handler' in path.name and path.parent.name == 'json':
|
||||
checks = check_json_handler_config(path, content, bypass_rules)
|
||||
elif 'apps/modules' in str(path):
|
||||
checks = check_module_json_files(path, content, bypass_rules)
|
||||
path_str = str(path)
|
||||
|
||||
if not checks:
|
||||
# --- Case (a): json_handler.py in a json/ directory ---
|
||||
if 'json_handler' in path.name and path.parent.name == 'json':
|
||||
checks = _check_json_handler_config(path, content, bypass_rules)
|
||||
passed_count = sum(1 for c in checks if c['passed'])
|
||||
total = len(checks)
|
||||
score = int((passed_count / total * 100)) if total > 0 else 0
|
||||
return {
|
||||
'passed': score >= 75,
|
||||
'checks': checks,
|
||||
'score': score,
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
# --- Determine if the file is in modules/ or handlers/ ---
|
||||
in_modules = 'apps/modules' in path_str or 'apps\\modules' in path_str
|
||||
in_handlers = 'apps/handlers' in path_str or 'apps\\handlers' in path_str
|
||||
|
||||
# Exclude files inside the json/ handler directory itself (they ARE the
|
||||
# json infrastructure, not consumers of it)
|
||||
if in_handlers and path.parent.name == 'json':
|
||||
return {
|
||||
'passed': True,
|
||||
'checks': [{'name': 'JSON structure check', 'passed': True, 'message': 'No JSON operations detected (skipped)'}],
|
||||
'checks': [{'name': 'JSON structure check', 'passed': True,
|
||||
'message': 'JSON handler infrastructure file (not applicable)'}],
|
||||
'score': 100,
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
passed_checks = sum(1 for check in checks if check['passed'])
|
||||
total_checks = len(checks)
|
||||
score = int((passed_checks / total_checks * 100)) if total_checks > 0 else 0
|
||||
overall_passed = score >= 75
|
||||
# --- Cases (b) and (c): code wiring check ---
|
||||
if in_modules or in_handlers:
|
||||
checks = _check_code_wiring(path, content)
|
||||
passed_count = sum(1 for c in checks if c['passed'])
|
||||
total = len(checks)
|
||||
score = int((passed_count / total * 100)) if total > 0 else 0
|
||||
return {
|
||||
'passed': passed_count == total,
|
||||
'checks': checks,
|
||||
'score': score,
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
# --- Case (d): file outside modules/ and handlers/ (entry point, etc.) ---
|
||||
return {
|
||||
'passed': overall_passed,
|
||||
'checks': checks,
|
||||
'score': score,
|
||||
'passed': True,
|
||||
'checks': [{'name': 'JSON structure check', 'passed': True,
|
||||
'message': 'Not in modules/ or handlers/ (not applicable)'}],
|
||||
'score': 100,
|
||||
'standard': 'JSON STRUCTURE'
|
||||
}
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _check_code_wiring(_path: Path, content: str) -> List[Dict]:
|
||||
"""
|
||||
Check that a module/handler file has the three-JSON wiring:
|
||||
1. Imports json_handler
|
||||
2. Calls json_handler.log_operation()
|
||||
|
||||
Returns a list of two check dicts.
|
||||
"""
|
||||
checks: List[Dict] = []
|
||||
|
||||
# Check 1: imports json_handler
|
||||
# Matches patterns like:
|
||||
# from aipass.seedgo.apps.handlers.json import json_handler
|
||||
# from aipass.flow.apps.handlers.json import json_handler
|
||||
# from ...handlers.json import json_handler
|
||||
has_import = bool(
|
||||
re.search(r'from\s+\S*\.json\s+import\s+json_handler', content)
|
||||
or re.search(r'from\s+\S*json\s+import\s+json_handler', content)
|
||||
or re.search(r'import\s+json_handler', content)
|
||||
)
|
||||
checks.append({
|
||||
'name': 'json_handler import',
|
||||
'passed': has_import,
|
||||
'message': 'Imports json_handler' if has_import
|
||||
else 'Missing json_handler import — add: from aipass.<branch>.apps.handlers.json import json_handler'
|
||||
})
|
||||
|
||||
# Check 2: calls json_handler.log_operation()
|
||||
has_log_operation = 'json_handler.log_operation' in content
|
||||
checks.append({
|
||||
'name': 'log_operation call',
|
||||
'passed': has_log_operation,
|
||||
'message': 'Calls json_handler.log_operation()' if has_log_operation
|
||||
else 'Missing json_handler.log_operation() call — every module/handler must log operations'
|
||||
})
|
||||
|
||||
return checks
|
||||
|
||||
|
||||
def _check_json_handler_config(_handler_path: Path, content: str, _bypass_rules: list | None = None) -> List[Dict]:
|
||||
"""
|
||||
Check json_handler.py for library profile.
|
||||
|
||||
For pip packages:
|
||||
- PASS if using relative paths (Path(__file__).resolve().parent)
|
||||
- FAIL if using hardcoded absolute paths (Path.home(), BRANCH_ROOT, etc.)
|
||||
"""
|
||||
checks: List[Dict] = []
|
||||
|
||||
# Check 1: No hardcoded absolute paths
|
||||
has_path_home = bool(re.search(r'Path\.home\(\)', content))
|
||||
# Only flag _ROOT constants that use Path.home() (legacy pattern)
|
||||
# Allow _ROOT = Path(__file__).resolve()... (relative, pip-safe)
|
||||
has_branch_root = False
|
||||
for line in content.split('\n'):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('#'):
|
||||
continue
|
||||
if re.search(r'\w+_ROOT\s*=', stripped) and 'Path.home()' in stripped:
|
||||
has_branch_root = True
|
||||
break
|
||||
|
||||
if has_path_home or has_branch_root:
|
||||
issues = []
|
||||
if has_path_home:
|
||||
issues.append('Path.home()')
|
||||
if has_branch_root:
|
||||
issues.append('hardcoded _ROOT constant')
|
||||
checks.append({
|
||||
'name': 'No absolute paths',
|
||||
'passed': False,
|
||||
'message': f'Found {", ".join(issues)} — pip packages should use relative paths'
|
||||
})
|
||||
else:
|
||||
checks.append({
|
||||
'name': 'No absolute paths',
|
||||
'passed': True,
|
||||
'message': 'No hardcoded absolute paths (correct for pip packages)'
|
||||
})
|
||||
|
||||
# Check 2: Uses relative path resolution
|
||||
has_relative = bool(re.search(r'Path\(__file__\)', content)
|
||||
or re.search(r'\.resolve\(\)', content)
|
||||
or re.search(r'\.parent', content))
|
||||
|
||||
checks.append({
|
||||
'name': 'Relative path resolution',
|
||||
'passed': has_relative,
|
||||
'message': 'Uses relative path resolution (Path(__file__).parent)' if has_relative
|
||||
else 'Missing relative path resolution — should use Path(__file__).resolve().parent'
|
||||
})
|
||||
|
||||
return checks
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Utility functions (used by other code in the audit system)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _find_registry() -> Path:
|
||||
"""Find AIPASS_REGISTRY.json by walking up from this file's location."""
|
||||
current = Path(__file__).resolve().parent
|
||||
for parent in [current] + list(current.parents):
|
||||
for parent in [current, *list(current.parents)]:
|
||||
candidate = parent / "AIPASS_REGISTRY.json"
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
@@ -169,123 +330,3 @@ def get_branch_path(branch_name: str) -> Optional[str]:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def check_json_handler_config(handler_path: Path, content: str, bypass_rules: list | None = None) -> List[Dict]: # noqa: ARG001
|
||||
"""
|
||||
Check json_handler.py for library profile.
|
||||
|
||||
For pip packages:
|
||||
- PASS if using relative paths (Path(__file__).resolve().parent, etc.)
|
||||
- FAIL if using hardcoded absolute paths (Path.home(), BRANCH_ROOT, etc.)
|
||||
"""
|
||||
checks = []
|
||||
|
||||
# Check 1: No hardcoded absolute paths
|
||||
has_path_home = bool(re.search(r'Path\.home\(\)', content))
|
||||
# Only flag _ROOT constants that use Path.home() (legacy pattern)
|
||||
# Allow _ROOT = Path(__file__).resolve()... (relative, pip-safe)
|
||||
has_branch_root = False
|
||||
for line in content.split('\n'):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith('#'):
|
||||
continue
|
||||
if re.search(r'\w+_ROOT\s*=', stripped) and 'Path.home()' in stripped:
|
||||
has_branch_root = True
|
||||
break
|
||||
|
||||
if has_path_home or has_branch_root:
|
||||
issues = []
|
||||
if has_path_home:
|
||||
issues.append('Path.home()')
|
||||
if has_branch_root:
|
||||
issues.append('hardcoded _ROOT constant')
|
||||
checks.append({
|
||||
'name': 'No absolute paths',
|
||||
'passed': False,
|
||||
'message': f'Found {", ".join(issues)} — pip packages should use relative paths'
|
||||
})
|
||||
else:
|
||||
checks.append({
|
||||
'name': 'No absolute paths',
|
||||
'passed': True,
|
||||
'message': 'No hardcoded absolute paths (correct for pip packages)'
|
||||
})
|
||||
|
||||
# Check 2: Uses relative path resolution
|
||||
has_relative = bool(re.search(r'Path\(__file__\)', content) or
|
||||
re.search(r'\.resolve\(\)', content) or
|
||||
re.search(r'\.parent', content))
|
||||
|
||||
checks.append({
|
||||
'name': 'Relative path resolution',
|
||||
'passed': has_relative,
|
||||
'message': 'Uses relative path resolution (Path(__file__).parent)' if has_relative
|
||||
else 'Missing relative path resolution — should use Path(__file__).resolve().parent'
|
||||
})
|
||||
|
||||
return checks
|
||||
|
||||
|
||||
def check_module_json_files(module_path: Path, content: str, bypass_rules: list | None = None) -> List[Dict]:
|
||||
"""
|
||||
Check if module uses json_handler for JSON operations.
|
||||
For library profile, just validates json_handler usage pattern.
|
||||
"""
|
||||
checks = []
|
||||
|
||||
uses_json_handler = 'json_handler' in content and ('from' in content or 'import' in content)
|
||||
|
||||
if not uses_json_handler:
|
||||
json_check = check_json_handler_usage(content, str(module_path), bypass_rules)
|
||||
if json_check:
|
||||
checks.append(json_check)
|
||||
return checks
|
||||
|
||||
# Module uses json_handler — that's correct
|
||||
checks.append({
|
||||
'name': 'json_handler usage',
|
||||
'passed': True,
|
||||
'message': 'Module uses json_handler for JSON operations'
|
||||
})
|
||||
|
||||
return checks
|
||||
|
||||
|
||||
def check_json_handler_usage(content: str, file_path: str, bypass_rules: list | None = None) -> Optional[Dict]:
|
||||
"""
|
||||
Check that modules use json_handler for JSON operations (not direct json.load/dump).
|
||||
"""
|
||||
try:
|
||||
tree = ast.parse(content)
|
||||
except SyntaxError:
|
||||
return None
|
||||
|
||||
has_json_operations = False
|
||||
has_json_handler_import = False
|
||||
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom):
|
||||
if node.module and 'json_handler' in node.module:
|
||||
has_json_handler_import = True
|
||||
|
||||
if isinstance(node, ast.Call):
|
||||
if isinstance(node.func, ast.Attribute):
|
||||
if (isinstance(node.func.value, ast.Name) and
|
||||
node.func.value.id == 'json' and
|
||||
node.func.attr in ['dump', 'load']):
|
||||
has_json_operations = True
|
||||
|
||||
if not has_json_operations:
|
||||
return None
|
||||
|
||||
if has_json_operations and not has_json_handler_import:
|
||||
if is_bypassed(file_path, 'json_structure', bypass_rules=bypass_rules):
|
||||
return None
|
||||
return {
|
||||
'name': 'json_handler usage',
|
||||
'passed': False,
|
||||
'message': 'Direct JSON operations detected (use json_handler for structured JSON management)'
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: json_structure_content.py
|
||||
# Description: JSON Structure Standards Content Handler
|
||||
# Version: 1.0.0
|
||||
# Version: 2.0.0
|
||||
# Created: 2026-03-05
|
||||
# Modified: 2026-03-05
|
||||
# Modified: 2026-03-17
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -13,74 +13,118 @@ Provides formatted JSON structure standards content.
|
||||
Module orchestrates, handler implements.
|
||||
"""
|
||||
|
||||
|
||||
def get_json_structure_standards() -> str:
|
||||
"""Return formatted JSON structure standards content with Rich markup
|
||||
"""Return formatted JSON structure standards content with Rich markup.
|
||||
|
||||
Returns:
|
||||
str: Formatted standards text with Rich styling
|
||||
"""
|
||||
lines = [
|
||||
"[bold red]JSON STRUCTURE STANDARD[/bold red]",
|
||||
"",
|
||||
"[bold cyan]CORE PRINCIPLE:[/bold cyan] Operational JSON Output",
|
||||
"",
|
||||
"[yellow]RULE:[/yellow] Every module and handler produces operational JSON",
|
||||
" output via json_handler. The CODE PATTERN is the standard --",
|
||||
" not JSON file templates. Files auto-create on first use.",
|
||||
"",
|
||||
"─" * 70,
|
||||
"",
|
||||
"[bold cyan]THREE-JSON PATTERN:[/bold cyan]",
|
||||
"",
|
||||
"[bold]Every module gets 3 auto-created files:[/bold]",
|
||||
" • [yellow]module_config.json[/yellow] - Settings, limits (small, stable)",
|
||||
" • [yellow]module_data.json[/yellow] - Metrics, state (medium, periodic)",
|
||||
" • [yellow]module_log.json[/yellow] - Operations history (auto-rotating)",
|
||||
" Every module/handler gets 3 auto-created files:",
|
||||
" [yellow]config.json[/yellow] - Settings, limits (small, stable)",
|
||||
" [yellow]data.json[/yellow] - Metrics, state (medium, periodic)",
|
||||
" [yellow]log.json[/yellow] - Operations history (auto-rotating)",
|
||||
"",
|
||||
"[yellow]RULE:[/yellow] Auto-created on first use - NEVER create manually",
|
||||
" [bold]Location:[/bold] All JSON goes to [green]{branch}_json/[/green] at branch root",
|
||||
" [dim]src/aipass/{branch}/{branch}_json/[/dim]",
|
||||
" One directory, no splits.",
|
||||
"",
|
||||
"[bold cyan]USAGE (AUTO-DETECTION):[/bold cyan]",
|
||||
" [dim]from aipass.seedgo.apps.handlers.json import json_handler[/dim]",
|
||||
"─" * 70,
|
||||
"",
|
||||
" [dim]# Handler auto-detects calling module name:[/dim]",
|
||||
" [dim]json_handler.log_operation(\"operation_name\", {\"key\": \"value\"})[/dim]",
|
||||
"[bold cyan]REQUIRED CODE WIRING:[/bold cyan]",
|
||||
"",
|
||||
" [dim]# Creates module_config.json, module_data.json, module_log.json[/dim]",
|
||||
" [dim]# Based on templates in apps/json_templates/default/[/dim]",
|
||||
" This is the standard. This is what agents copy.",
|
||||
"",
|
||||
" [dim]from aipass.{branch}.apps.handlers.json import json_handler[/dim]",
|
||||
"",
|
||||
" [dim]# At end of main operations:[/dim]",
|
||||
" [dim]json_handler.log_operation(\"operation_name\", {[/dim]",
|
||||
" [dim] \"key\": \"value\",[/dim]",
|
||||
" [dim] \"success\": True[/dim]",
|
||||
" [dim]})[/dim]",
|
||||
"",
|
||||
" [bold]Example (flow branch):[/bold]",
|
||||
" [dim]from aipass.flow.apps.handlers.json import json_handler[/dim]",
|
||||
"",
|
||||
" [dim]json_handler.log_operation(\"plan_created\", {[/dim]",
|
||||
" [dim] \"command\": command,[/dim]",
|
||||
" [dim] \"args\": args[/dim]",
|
||||
" [dim]})[/dim]",
|
||||
"",
|
||||
" [yellow]Both the import AND the log_operation call are required.[/yellow]",
|
||||
"",
|
||||
"─" * 70,
|
||||
"",
|
||||
"[bold cyan]json_handler.py SETUP:[/bold cyan]",
|
||||
"",
|
||||
" Auto-detects branch via [dim]Path(__file__)[/dim]. Universal across branches:",
|
||||
"",
|
||||
" [dim]_BRANCH_ROOT = Path(__file__).resolve().parents[3][/dim]",
|
||||
" [dim]_BRANCH_NAME = _BRANCH_ROOT.name[/dim]",
|
||||
" [dim]JSON_DIR = _BRANCH_ROOT / f\"{_BRANCH_NAME}_json\"[/dim]",
|
||||
"",
|
||||
" [green]No per-branch customization needed.[/green]",
|
||||
" Spawn ships the template, branches just copy it.",
|
||||
"",
|
||||
"─" * 70,
|
||||
"",
|
||||
"[bold cyan]SCORING:[/bold cyan]",
|
||||
"",
|
||||
" [bold]2 checks per module/handler:[/bold]",
|
||||
" 1. [dim]json_handler[/dim] import present",
|
||||
" 2. [dim]log_operation()[/dim] call present",
|
||||
"",
|
||||
" Both must pass. Score = compliant files / total files * 100",
|
||||
"",
|
||||
"─" * 70,
|
||||
"",
|
||||
"[bold cyan]WHAT'S CHECKED:[/bold cyan]",
|
||||
"",
|
||||
" [green]Checked:[/green]",
|
||||
" [green]+[/green] Modules: [dim]apps/modules/*.py[/dim]",
|
||||
" [green]+[/green] Handlers: [dim]apps/handlers/**/*.py[/dim]",
|
||||
"",
|
||||
" [red]Skipped:[/red]",
|
||||
" [red]-[/red] [dim]__init__.py[/dim] files (structural, not functional)",
|
||||
" [red]-[/red] Entry points (not required to wire JSON)",
|
||||
" [red]-[/red] [dim]json_handler.py[/dim] itself (checked for path validation only)",
|
||||
"",
|
||||
"─" * 70,
|
||||
"",
|
||||
"[bold cyan]LOG ROTATION:[/bold cyan]",
|
||||
"",
|
||||
" Set [dim]max_log_entries[/dim] in config.json.",
|
||||
" Handler auto-rotates when limit reached (FIFO).",
|
||||
" Keeps most recent N entries.",
|
||||
"",
|
||||
"[bold cyan]LOG ROTATION (CRITICAL):[/bold cyan]",
|
||||
" Set in config.json:",
|
||||
" [dim]{[/dim]",
|
||||
" [dim]\"config\": { \"max_log_entries\": 100 }[/dim]",
|
||||
" [dim] \"config\": { \"max_log_entries\": 100 }[/dim]",
|
||||
" [dim]}[/dim]",
|
||||
"",
|
||||
" • Handler auto-rotates when limit reached",
|
||||
" • Keeps most recent N entries (FIFO)",
|
||||
" • [yellow]⚠️ Prevents log explosion (drone logs hit 171KB/7001 entries)[/yellow]",
|
||||
"─" * 70,
|
||||
"",
|
||||
"[bold cyan]LOG STRUCTURE:[/bold cyan]",
|
||||
" Array of entries (NOT object with \"entries\" key):",
|
||||
" [dim][[/dim]",
|
||||
" [dim]{ \"timestamp\": \"...\", \"operation\": \"...\", \"data\": {...} }[/dim]",
|
||||
" [dim]][/dim]",
|
||||
"",
|
||||
"[bold cyan]LOCATIONS:[/bold cyan]",
|
||||
" • Package JSON: [dim]src/aipass/{module}/aipass_json/[/dim]",
|
||||
" • Per-module: [dim]src/aipass/{module}/{module}_json/[/dim]",
|
||||
"",
|
||||
"[bold red]SETUP json_handler.py (MANDATORY):[/bold red]",
|
||||
" [red]✗ DO NOT copy seedgo's handler without changing paths![/red]",
|
||||
"",
|
||||
" [green]✓ Update BRANCH_ROOT:[/green]",
|
||||
" [dim]{BRANCH}_ROOT = Path(__file__).resolve().parents[N][/dim]",
|
||||
"",
|
||||
" [green]✓ Update JSON_DIR:[/green]",
|
||||
" [dim]{BRANCH}_JSON_DIR = {BRANCH}_ROOT / \"{branch}_json\"[/dim]",
|
||||
"",
|
||||
" [green]✓ Update TEMPLATES_DIR:[/green]",
|
||||
" [dim]JSON_TEMPLATES_DIR = {BRANCH}_ROOT / \"apps\" / \"json_templates\"[/dim]",
|
||||
"",
|
||||
" [yellow]Validate:[/yellow] Run standards checker on json_handler.py",
|
||||
" [dim]Expected: 100/100 on JSON STRUCTURE standard[/dim]",
|
||||
"",
|
||||
"[bold cyan]SPECIAL JSONS:[/bold cyan]",
|
||||
" • [bold]Registries:[/bold] Collections (AIPASS_REGISTRY.json)",
|
||||
" • [bold]Custom:[/bold] Module-specific needs (no limit)",
|
||||
" • [dim]Not part of three-JSON pattern[/dim]",
|
||||
"[bold cyan]KEY WARNINGS:[/bold cyan]",
|
||||
" [yellow]![/yellow] The CODE PATTERN is the template -- no json_templates/ directory",
|
||||
" [yellow]![/yellow] JSON files auto-create on first log_operation() call",
|
||||
" [yellow]![/yellow] Never create JSON files manually",
|
||||
" [yellow]![/yellow] Both import AND log_operation required for compliance",
|
||||
"",
|
||||
"[bold cyan]REFERENCE:[/bold cyan]",
|
||||
" [dim]See: seedgo standards pack (json_structure)[/dim]",
|
||||
" [dim]See: src/aipass/seedgo/apps/standards/aipass/handlers/json/json_handler.py[/dim]",
|
||||
" [dim]See: FPLAN-0056 (Three-JSON Pattern Redesign)[/dim]",
|
||||
]
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: log_structure_check.py
|
||||
# Description: Log Structure Standards Checker Handler
|
||||
# Version: 1.1.0
|
||||
# Version: 1.2.0
|
||||
# Created: 2026-03-06
|
||||
# Modified: 2026-03-08
|
||||
# Modified: 2026-03-17
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Log Structure Standards Checker Handler
|
||||
|
||||
Validates that AIPass modules follow hierarchical log placement:
|
||||
every directory containing .py code should have a sibling logs/ directory.
|
||||
Validates the two-tier logging model:
|
||||
- system_logs/ at repo root (system-wide)
|
||||
- logs/ at branch root only (per-branch)
|
||||
No hierarchical logs/ at every nested directory.
|
||||
No hardcoded absolute log paths.
|
||||
"""
|
||||
|
||||
@@ -37,10 +39,10 @@ def is_bypassed(file_path: str, standard: str, bypass_rules: list | None = None)
|
||||
|
||||
def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
|
||||
"""
|
||||
Check module logging structure with hierarchical placement validation.
|
||||
Check module logging structure against the two-tier model.
|
||||
|
||||
Checks:
|
||||
1. Parent directory of file has a sibling logs/ directory (hierarchical placement)
|
||||
1. logs/ directory exists at branch root (entry file's parent)
|
||||
2. No hardcoded absolute log paths in source
|
||||
3. No /home/ references in logging configuration
|
||||
|
||||
@@ -70,16 +72,16 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
|
||||
'standard': 'LOG_STRUCTURE'
|
||||
}
|
||||
|
||||
# Check 1: Hierarchical log placement — logs/ directory at the same level as the file
|
||||
parent_dir = path.parent
|
||||
logs_dir = parent_dir / "logs"
|
||||
# Check 1: Branch-root log placement — logs/ directory at the branch root
|
||||
branch_root = path.parent
|
||||
logs_dir = branch_root / "logs"
|
||||
has_logs_dir = logs_dir.is_dir()
|
||||
checks.append({
|
||||
'name': 'Hierarchical logs/ directory',
|
||||
'name': 'Branch-root logs/ directory',
|
||||
'passed': has_logs_dir,
|
||||
'message': f'logs/ directory exists at {parent_dir}/'
|
||||
'message': f'logs/ directory exists at branch root {branch_root}/'
|
||||
if has_logs_dir
|
||||
else f'Missing logs/ directory at {parent_dir}/ — hierarchical placement requires logs/ at every code level'
|
||||
else f'Missing logs/ directory at branch root {branch_root}/ — two-tier model requires logs/ at branch root'
|
||||
})
|
||||
|
||||
# Check 2-3: Scan file for hardcoded log paths
|
||||
|
||||
@@ -1,62 +1,57 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: log_structure_content.py
|
||||
# Description: Log Structure Standards Content Handler
|
||||
# Version: 1.1.0
|
||||
# Version: 1.2.0
|
||||
# Created: 2026-03-06
|
||||
# Modified: 2026-03-08
|
||||
# Modified: 2026-03-17
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
Log Structure Standards Content Handler
|
||||
|
||||
Provides formatted display of log structure standards for terminal output.
|
||||
Describes the hierarchical log placement model where every directory
|
||||
containing .py code has a sibling logs/ directory.
|
||||
Describes the two-tier logging model: system_logs/ at repo root,
|
||||
logs/ at branch root only.
|
||||
"""
|
||||
|
||||
def get_log_structure_standards() -> str:
|
||||
"""Return formatted log structure standards for display."""
|
||||
lines = [
|
||||
"[bold red]HIERARCHICAL LOG STRUCTURE[/bold red]",
|
||||
"[bold red]TWO-TIER LOG STRUCTURE[/bold red]",
|
||||
"",
|
||||
"[yellow]CORE PRINCIPLE:[/yellow] Logs live where the code lives — every directory",
|
||||
" containing .py files should have a sibling [bold]logs/[/bold] directory",
|
||||
"[yellow]CORE PRINCIPLE:[/yellow] Two tiers, two locations. System-wide logs aggregate",
|
||||
" at repo root; per-branch logs live at the branch root only.",
|
||||
"",
|
||||
"[bold cyan]DUAL LOGGING MODEL:[/bold cyan]",
|
||||
" [green]1.[/green] [bold]system_logs/[/bold] at repo root — central aggregation ({branch}_{module}.log)",
|
||||
" [green]2.[/green] [bold]logs/[/bold] at every code level — hierarchical local placement",
|
||||
"[bold cyan]TWO-TIER MODEL:[/bold cyan]",
|
||||
" [green]1.[/green] [bold]system_logs/[/bold] at repo root — system-wide aggregation ({branch}_{module}.log)",
|
||||
" [green]2.[/green] [bold]logs/[/bold] at branch root — per-branch local logs",
|
||||
"",
|
||||
"[bold cyan]REQUIREMENTS:[/bold cyan]",
|
||||
" [green]1.[/green] Every directory with .py files has a sibling [bold]logs/[/bold] directory",
|
||||
" [green]1.[/green] [bold]logs/[/bold] directory exists at the branch root",
|
||||
" [green]2.[/green] No hardcoded absolute paths to .log files",
|
||||
" [green]3.[/green] No /home/ references in logging configuration",
|
||||
"",
|
||||
"[bold cyan]HIERARCHICAL DIRECTORY PATTERN:[/bold cyan]",
|
||||
" [dim]src/aipass/{branch}/[/dim]",
|
||||
" [dim] logs/ <-- branch root logs[/dim]",
|
||||
" [dim] apps/[/dim]",
|
||||
" [dim] logs/ <-- entry point level[/dim]",
|
||||
" [dim] modules/[/dim]",
|
||||
" [dim] logs/ <-- module level[/dim]",
|
||||
" [dim] handlers/[/dim]",
|
||||
" [dim] logs/ <-- handler root[/dim]",
|
||||
" [dim] dispatch/[/dim]",
|
||||
" [dim] logs/ <-- sub-handler level[/dim]",
|
||||
" [dim] email/[/dim]",
|
||||
" [dim] logs/ <-- sub-handler level[/dim]",
|
||||
"[bold cyan]DIRECTORY LAYOUT:[/bold cyan]",
|
||||
" [dim]repo_root/[/dim]",
|
||||
" [dim] system_logs/ <-- system-wide logs[/dim]",
|
||||
" [dim] src/aipass/{branch}/[/dim]",
|
||||
" [dim] logs/ <-- branch-level logs (only here)[/dim]",
|
||||
" [dim] apps/[/dim]",
|
||||
" [dim] handlers/[/dim]",
|
||||
" [dim] modules/[/dim]",
|
||||
"",
|
||||
"[bold cyan]LOG PATH PATTERN:[/bold cyan]",
|
||||
" [green]CORRECT:[/green] Use prax's system_logger (auto-routes to correct location)",
|
||||
" [green]CORRECT:[/green] Use prax's system_logger (auto-routes to correct tier)",
|
||||
" [dim] from aipass.prax.apps.modules.logger import system_logger[/dim]",
|
||||
" [dim] system_logger.info('message') # prax handles hierarchical placement[/dim]",
|
||||
" [dim] system_logger.info('message') # prax handles tier routing[/dim]",
|
||||
"",
|
||||
" [green]ALSO OK:[/green] Manual relative path to sibling logs/",
|
||||
" [dim] LOG_DIR = Path(__file__).resolve().parent / 'logs'[/dim]",
|
||||
" [green]ALSO OK:[/green] Relative path to branch-root logs/",
|
||||
" [dim] LOG_DIR = Path(__file__).resolve().parents[2] / 'logs'[/dim]",
|
||||
" [dim] log_path = LOG_DIR / 'my_handler.log'[/dim]",
|
||||
"",
|
||||
" [red]WRONG:[/red] All logs dumped at branch root only",
|
||||
" [dim] LOG_DIR = Path(__file__).resolve().parents[3] / 'logs'[/dim]",
|
||||
" [dim] # All handlers write to branch/logs/ — no hierarchy[/dim]",
|
||||
" [red]WRONG:[/red] logs/ at nested directories (old hierarchical pattern)",
|
||||
" [dim] LOG_DIR = Path(__file__).resolve().parent / 'logs'[/dim]",
|
||||
" [dim] # Creates logs/ inside handlers/ or modules/ — not two-tier[/dim]",
|
||||
"",
|
||||
" [red]WRONG:[/red] Hardcoded absolute paths",
|
||||
" [dim] path = '/absolute/path/to/system_logs/module.log'[/dim]",
|
||||
@@ -64,6 +59,5 @@ def get_log_structure_standards() -> str:
|
||||
"",
|
||||
"[bold cyan]REFERENCE:[/bold cyan]",
|
||||
" [dim]Prax logger: src/aipass/prax/apps/modules/logger.py[/dim]",
|
||||
" [dim]Hierarchical resolver: src/aipass/prax/apps/handlers/config/load.py[/dim]",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: meta_check.py
|
||||
# Description: META Block Standards Checker Handler
|
||||
# Version: 1.1.0
|
||||
# Version: 1.2.0
|
||||
# Created: 2026-03-05
|
||||
# Modified: 2026-03-08
|
||||
# Modified: 2026-03-16
|
||||
# =============================================
|
||||
|
||||
"""
|
||||
@@ -30,6 +30,7 @@ import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
AUDIT_SCOPE = "all_files"
|
||||
|
||||
# Header/footer markers for library META
|
||||
# Accept both AIPass (canonical) and META (legacy) header markers
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
# =================== AIPass ====================
|
||||
# Name: json_handler.py
|
||||
# Description: Json Handler
|
||||
# Version: 1.0.0
|
||||
# Description: Auto-Creating JSON Handler
|
||||
# Version: 1.1.0
|
||||
# Created: 2026-03-05
|
||||
# Modified: 2026-03-05
|
||||
# Modified: 2026-03-17
|
||||
# =============================================
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
import sys
|
||||
from typing import Dict, Any, Optional
|
||||
import inspect
|
||||
|
||||
PACK_ROOT = Path(__file__).resolve().parent.parent.parent # json/ -> handlers/ -> aipass/
|
||||
SEEDGO_JSON_DIR = PACK_ROOT / "aipass_json"
|
||||
JSON_TEMPLATES_DIR = PACK_ROOT / "json_templates"
|
||||
_BRANCH_ROOT = Path(__file__).resolve().parents[3] # json/ -> handlers/ -> apps/ -> {branch}/
|
||||
_BRANCH_NAME = _BRANCH_ROOT.name
|
||||
JSON_DIR = _BRANCH_ROOT / f"{_BRANCH_NAME}_json"
|
||||
JSON_TEMPLATES_DIR = _BRANCH_ROOT / "apps" / "json_templates"
|
||||
|
||||
|
||||
def _get_caller_module_name() -> str:
|
||||
@@ -82,12 +82,12 @@ def validate_json_structure(data: Any, json_type: str) -> bool:
|
||||
def get_json_path(module_name: str, json_type: str) -> Path:
|
||||
"""Get path for module JSON file"""
|
||||
filename = f"{module_name}_{json_type}.json"
|
||||
return SEEDGO_JSON_DIR / filename
|
||||
return JSON_DIR / filename
|
||||
|
||||
|
||||
def ensure_json_exists(module_name: str, json_type: str) -> bool:
|
||||
"""Ensure JSON file exists, create from template if missing"""
|
||||
SEEDGO_JSON_DIR.mkdir(parents=True, exist_ok=True)
|
||||
JSON_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
json_path = get_json_path(module_name, json_type)
|
||||
|
||||
@@ -238,16 +238,17 @@ if __name__ == "__main__":
|
||||
border_style="bright_blue"
|
||||
))
|
||||
console.print()
|
||||
console.print("[yellow]TESTING:[/yellow] Creating seedgo JSONs...")
|
||||
console.print(f"[yellow]TESTING:[/yellow] Creating {_BRANCH_NAME} JSONs...")
|
||||
console.print(f"[dim]JSON_DIR: {JSON_DIR}[/dim]")
|
||||
|
||||
# Test auto-creation
|
||||
log_operation("test_operation", {"test": "data"}, "seedgo")
|
||||
increment_counter("seedgo", "test_counter", 1)
|
||||
update_data_metrics("seedgo", test_metric="working")
|
||||
log_operation("test_operation", {"test": "data"}, _BRANCH_NAME)
|
||||
increment_counter(_BRANCH_NAME, "test_counter", 1)
|
||||
update_data_metrics(_BRANCH_NAME, test_metric="working")
|
||||
|
||||
console.print()
|
||||
console.print("[green]Check aipass_json/ directory for created files:[/green]")
|
||||
console.print(" [dim]•[/dim] seedgo_config.json")
|
||||
console.print(" [dim]•[/dim] seedgo_data.json")
|
||||
console.print(" [dim]•[/dim] seedgo_log.json")
|
||||
console.print(f"[green]Check {JSON_DIR.relative_to(_BRANCH_ROOT)}/ for created files:[/green]")
|
||||
console.print(f" [dim]•[/dim] {_BRANCH_NAME}_config.json")
|
||||
console.print(f" [dim]•[/dim] {_BRANCH_NAME}_data.json")
|
||||
console.print(f" [dim]•[/dim] {_BRANCH_NAME}_log.json")
|
||||
console.print()
|
||||
|
||||
Reference in New Issue
Block a user