feat(seedgo): standards enforcement system — checklist command, checker fixes, handler guards

FPLAN-0048: Seedgo Reach — 4-phase standards enforcement overhaul.

Phase 1: Checker improvements
- introspection_check.py: module-level handle_command() gate validation
  (catches modules that execute instead of showing introspection on no-args)
- encapsulation_check.py: handler guard presence detection
  (flags branches with empty handlers/__init__.py)

Phase 2: Checklist command
- New checklist.py module: `drone @seedgo checklist <file>`
- Runs applicable standards per-file, concise pass/fail output
- 23 standards on entry points, 3 (all_files scope) on modules
- Designed for auto-fix hook consumption

Phase 3: Auto-fix hook integration (separate from this commit)

Phase 4: Handler guard deployment
- Deployed runtime import guards to 6 previously unprotected branches
  (backup, daemon, drone, memory, spawn, seedgo)
- All 12 handler-bearing branches now have active guards
- Uses inspect.stack() to block cross-branch handler imports

Co-Authored-By: @seedgo <seedgo@aipass>
This commit is contained in:
AIOSAI
2026-03-15 20:20:52 -07:00
co-authored by @seedgo
parent 07f2a6cf8f
commit 4e183f2bf8
11 changed files with 1394 additions and 6 deletions
+134 -1
View File
@@ -1 +1,134 @@
"""Backup system handlers package."""
"""Backup handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.backup"
def _find_real_caller():
"""
Walk the stack to find the actual file that triggered this import.
Skips:
- This file (handlers/__init__.py)
- Python's importlib internals
- Frozen modules
Returns tuple: (file_path, import_line) or (None, None)
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
# Skip this file
if this_file in str(Path(filename).resolve()):
continue
# Skip Python internals
if filename.startswith("<") or "importlib" in filename:
continue
# Found a real file - try to get the import line
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""
Block cross-branch handler imports.
Only code from within the 'backup' branch can import these handlers.
External branches must use aipass.backup.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
# DEBUG: Print what we found
import os
if os.environ.get("AIPASS_DEBUG_GUARD"):
import sys
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
if caller_file is None:
# Can't determine caller from real files
# Check if we're being run from command line (external)
# by looking at the raw stack for <string> or <stdin>
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
# Try to get the import line from the frame
target_line = "unknown"
if frame.code_context:
target_line = frame.code_context[0].strip()
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller: interactive/script\n"
f" Blocked: {target_line}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
return # Allow if truly can't determine
# Check if caller is from our branch
# MY_BRANCH is "aipass.backup" (dotted), but filesystem uses "/aipass/backup/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return # Same branch, allowed
# External caller - block access
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()
+134 -1
View File
@@ -1 +1,134 @@
"""Daemon handlers package."""
"""Daemon handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.daemon"
def _find_real_caller():
"""
Walk the stack to find the actual file that triggered this import.
Skips:
- This file (handlers/__init__.py)
- Python's importlib internals
- Frozen modules
Returns tuple: (file_path, import_line) or (None, None)
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
# Skip this file
if this_file in str(Path(filename).resolve()):
continue
# Skip Python internals
if filename.startswith("<") or "importlib" in filename:
continue
# Found a real file - try to get the import line
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""
Block cross-branch handler imports.
Only code from within the 'daemon' branch can import these handlers.
External branches must use aipass.daemon.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
# DEBUG: Print what we found
import os
if os.environ.get("AIPASS_DEBUG_GUARD"):
import sys
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
if caller_file is None:
# Can't determine caller from real files
# Check if we're being run from command line (external)
# by looking at the raw stack for <string> or <stdin>
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
# Try to get the import line from the frame
target_line = "unknown"
if frame.code_context:
target_line = frame.code_context[0].strip()
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller: interactive/script\n"
f" Blocked: {target_line}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
return # Allow if truly can't determine
# Check if caller is from our branch
# MY_BRANCH is "aipass.daemon" (dotted), but filesystem uses "/aipass/daemon/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return # Same branch, allowed
# External caller - block access
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()
+134
View File
@@ -0,0 +1,134 @@
"""Drone handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.drone"
def _find_real_caller():
"""
Walk the stack to find the actual file that triggered this import.
Skips:
- This file (handlers/__init__.py)
- Python's importlib internals
- Frozen modules
Returns tuple: (file_path, import_line) or (None, None)
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
# Skip this file
if this_file in str(Path(filename).resolve()):
continue
# Skip Python internals
if filename.startswith("<") or "importlib" in filename:
continue
# Found a real file - try to get the import line
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""
Block cross-branch handler imports.
Only code from within the 'drone' branch can import these handlers.
External branches must use aipass.drone.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
# DEBUG: Print what we found
import os
if os.environ.get("AIPASS_DEBUG_GUARD"):
import sys
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
if caller_file is None:
# Can't determine caller from real files
# Check if we're being run from command line (external)
# by looking at the raw stack for <string> or <stdin>
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
# Try to get the import line from the frame
target_line = "unknown"
if frame.code_context:
target_line = frame.code_context[0].strip()
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller: interactive/script\n"
f" Blocked: {target_line}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
return # Allow if truly can't determine
# Check if caller is from our branch
# MY_BRANCH is "aipass.drone" (dotted), but filesystem uses "/aipass/drone/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return # Same branch, allowed
# External caller - block access
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()
+134
View File
@@ -0,0 +1,134 @@
"""Memory handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.memory"
def _find_real_caller():
"""
Walk the stack to find the actual file that triggered this import.
Skips:
- This file (handlers/__init__.py)
- Python's importlib internals
- Frozen modules
Returns tuple: (file_path, import_line) or (None, None)
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
# Skip this file
if this_file in str(Path(filename).resolve()):
continue
# Skip Python internals
if filename.startswith("<") or "importlib" in filename:
continue
# Found a real file - try to get the import line
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""
Block cross-branch handler imports.
Only code from within the 'memory' branch can import these handlers.
External branches must use aipass.memory.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
# DEBUG: Print what we found
import os
if os.environ.get("AIPASS_DEBUG_GUARD"):
import sys
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
if caller_file is None:
# Can't determine caller from real files
# Check if we're being run from command line (external)
# by looking at the raw stack for <string> or <stdin>
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
# Try to get the import line from the frame
target_line = "unknown"
if frame.code_context:
target_line = frame.code_context[0].strip()
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller: interactive/script\n"
f" Blocked: {target_line}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
return # Allow if truly can't determine
# Check if caller is from our branch
# MY_BRANCH is "aipass.memory" (dotted), but filesystem uses "/aipass/memory/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return # Same branch, allowed
# External caller - block access
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()
+134
View File
@@ -0,0 +1,134 @@
"""Seedgo handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.seedgo"
def _find_real_caller():
"""
Walk the stack to find the actual file that triggered this import.
Skips:
- This file (handlers/__init__.py)
- Python's importlib internals
- Frozen modules
Returns tuple: (file_path, import_line) or (None, None)
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
# Skip this file
if this_file in str(Path(filename).resolve()):
continue
# Skip Python internals
if filename.startswith("<") or "importlib" in filename:
continue
# Found a real file - try to get the import line
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""
Block cross-branch handler imports.
Only code from within the 'seedgo' branch can import these handlers.
External branches must use aipass.seedgo.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
# DEBUG: Print what we found
import os
if os.environ.get("AIPASS_DEBUG_GUARD"):
import sys
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
if caller_file is None:
# Can't determine caller from real files
# Check if we're being run from command line (external)
# by looking at the raw stack for <string> or <stdin>
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
# Try to get the import line from the frame
target_line = "unknown"
if frame.code_context:
target_line = frame.code_context[0].strip()
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller: interactive/script\n"
f" Blocked: {target_line}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
return # Allow if truly can't determine
# Check if caller is from our branch
# MY_BRANCH is "aipass.seedgo" (dotted), but filesystem uses "/aipass/seedgo/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return # Same branch, allowed
# External caller - block access
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()
@@ -1,9 +1,9 @@
# =================== AIPass ====================
# Name: encapsulation_check.py
# Description: Handler Encapsulation Standards Checker
# Version: 1.0.0
# Version: 1.1.0
# Created: 2026-03-05
# Modified: 2026-03-05
# Modified: 2026-03-15
# =============================================
"""
@@ -13,6 +13,7 @@ Validates that handlers are properly encapsulated:
- No cross-branch handler imports (Branch A importing Branch B's handlers)
- No cross-package handler imports (handlers/X importing handlers/Y)
- Handlers should be accessed through module entry points, not directly
- Handler security guards present in handlers/__init__.py (inspect.stack guard)
"""
import re
@@ -149,6 +150,115 @@ def get_file_handler_package(file_path: str) -> Optional[str]:
return None
# Cache handler guard results per branch path (reset each audit run)
_handler_guard_cache: Dict[str, Optional[Dict]] = {}
def _resolve_branch_path(file_path: str) -> Optional[Path]:
"""Resolve the branch root directory for a given file path."""
branch_info = get_branch_from_path(file_path)
if not branch_info:
return None
raw_path = branch_info.get('path', '')
branch_path = Path(raw_path)
if not branch_path.is_absolute():
registry_path = _find_registry()
branch_path = (registry_path.parent / branch_path).resolve()
return branch_path
def check_handler_guard(module_path: str, bypass_rules: list | None = None) -> Optional[Dict]:
"""
Check that a branch's handlers/__init__.py contains a security guard.
The guard uses inspect.stack() to block cross-branch handler imports
at runtime. Branches without this guard have unprotected handlers.
Returns a check dict, or None if the check is not applicable
(e.g., no handlers/ directory in the branch).
"""
branch_path = _resolve_branch_path(module_path)
if branch_path is None:
return None
branch_key = str(branch_path)
# Return cached result if already checked this branch
if branch_key in _handler_guard_cache:
return _handler_guard_cache[branch_key]
# Check if bypassed
init_path = branch_path / "apps" / "handlers" / "__init__.py"
if is_bypassed(str(init_path), 'encapsulation', bypass_rules=bypass_rules):
result = {
'name': 'Handler security guard',
'passed': True,
'message': 'Handler guard check bypassed'
}
_handler_guard_cache[branch_key] = result
return result
handlers_dir = branch_path / "apps" / "handlers"
if not handlers_dir.is_dir():
# No handlers directory — check not applicable
_handler_guard_cache[branch_key] = None
return None
if not init_path.exists():
result = {
'name': 'Handler security guard',
'passed': False,
'message': 'Missing handlers/__init__.py — no handler security guard'
}
_handler_guard_cache[branch_key] = result
return result
# Read the init file and check for guard patterns
try:
content = init_path.read_text(encoding='utf-8')
except Exception:
result = {
'name': 'Handler security guard',
'passed': False,
'message': 'Cannot read handlers/__init__.py'
}
_handler_guard_cache[branch_key] = result
return result
# Count non-empty, non-comment lines
code_lines = [
ln for ln in content.split('\n')
if ln.strip() and not ln.strip().startswith('#')
]
# Guard detection: look for key patterns
guard_patterns = ['_guard_branch_access', 'inspect.stack', 'ImportError']
has_guard = any(pattern in content for pattern in guard_patterns)
if has_guard:
result = {
'name': 'Handler security guard',
'passed': True,
'message': 'Handler security guard present (inspect.stack guard active)'
}
elif len(code_lines) < 10:
result = {
'name': 'Handler security guard',
'passed': False,
'message': 'Missing handler security guard — cross-branch imports unprotected'
}
else:
# File has substantial code but no recognized guard patterns
result = {
'name': 'Handler security guard',
'passed': False,
'message': 'handlers/__init__.py has code but no recognized security guard pattern'
}
_handler_guard_cache[branch_key] = result
return result
def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
"""
Check if file respects handler encapsulation
@@ -226,6 +336,11 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
)
checks.append(direct_import_check)
# Check 4: Handler security guard presence (branch-level, cached)
guard_check = check_handler_guard(module_path, bypass_rules)
if guard_check is not None:
checks.append(guard_check)
# Calculate score
if not checks:
return {
@@ -15,12 +15,16 @@ Checks:
1. Entry points (apps/{name}.py): print_introspection function exists
2. Entry points: Execution order — no-args check before --help check in main()
3. Modules (apps/modules/*.py): print_introspection function exists
4. Modules: handle_command() no-args gate — must gate on empty args and call print_introspection()
"""
import ast
from pathlib import Path
from typing import Dict, Optional
# Run on ALL .py files so modules (apps/modules/*.py) are checked, not just entry points
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"""
@@ -157,6 +161,12 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
if dispatch_check:
checks.append(dispatch_check)
# Check 4: handle_command() no-args gate (modules only)
if is_module:
gate_check = check_module_handle_command_gate(tree, path.name)
if gate_check:
checks.append(gate_check)
# Calculate score
passed_checks = sum(1 for check in checks if check['passed'])
total_checks = len(checks)
@@ -296,6 +306,75 @@ def _find_main_function(tree: ast.Module) -> Optional[ast.FunctionDef]:
return None
def _find_handle_command_function(tree: ast.Module) -> Optional[ast.FunctionDef]:
"""Find the top-level handle_command() function definition."""
for node in tree.body:
if isinstance(node, ast.FunctionDef) and node.name == 'handle_command':
return node
return None
def check_module_handle_command_gate(tree: ast.Module, filename: str) -> Optional[Dict]:
"""
For modules (apps/modules/*.py), verify that handle_command() contains a
no-args gate that calls print_introspection().
The standard pattern is:
def handle_command(command, args):
...
if not args:
print_introspection() # or call a wrapper that shows introspection
return True
Without this gate, the module will immediately execute instead of showing
introspection when called with no arguments.
Uses AST to find handle_command(), then walks its body looking for a
no-args conditional whose body calls print_introspection (or a known
introspection wrapper).
"""
handle_cmd = _find_handle_command_function(tree)
if handle_cmd is None:
# No handle_command — module may use a different pattern, skip
return {
'name': 'handle_command no-args gate',
'passed': True,
'message': f'No handle_command() found in {filename} (skipped)'
}
# Walk handle_command body to find a no-args conditional that calls introspection
# Known introspection-related function names (direct or wrapper)
introspection_names = {
'print_introspection',
'_show_audit_introspection',
'_show_pack_module_introspection',
}
for node in ast.walk(handle_cmd):
if not isinstance(node, ast.If):
continue
if not _is_no_args_check(node):
continue
# Found a no-args gate — check if its body calls an introspection function
calls = _get_function_calls_in_block(node.body)
if calls & introspection_names:
return {
'name': 'handle_command no-args gate',
'passed': True,
'message': f'handle_command() gates on no-args at line {node.lineno} → introspection'
}
# No no-args gate found that dispatches to introspection
return {
'name': 'handle_command no-args gate',
'passed': False,
'message': f'handle_command() in {filename} has no no-args gate calling print_introspection() — module will not show introspection when called with no arguments'
}
def _find_name_main_block(tree: ast.Module) -> Optional[ast.If]:
"""
Find the if __name__ == '__main__': block at module level.
+381
View File
@@ -0,0 +1,381 @@
# =================== AIPass ====================
# Name: checklist.py
# Description: Per-File Standards Checklist Module
# Version: 1.0.0
# Created: 2026-03-15
# Modified: 2026-03-15
# =============================================
"""
Per-File Standards Checklist Module
Quick pass/fail standards check for a single file.
Designed for hook consumption — runs after every file edit.
Run: drone @seedgo checklist <file>
"""
import sys
from pathlib import Path
from typing import Dict, List, Optional
# =============================================================================
# INFRASTRUCTURE SETUP
# =============================================================================
# IMPORTS
# =============================================================================
# Prax logger (system-wide, always first)
from aipass.prax import logger
# CLI services (display/output formatting)
from aipass.cli import console
from aipass.cli.apps.modules import error
# Checker discovery (reuse existing infrastructure)
from aipass.seedgo.apps.handlers.audit.branch_audit import discover_checkers
# Bypass system
from aipass.seedgo.apps.handlers.bypass.bypass_handler import (
get_branch_from_path,
load_bypass_rules,
)
# =============================================================================
# CHECKER APPLICABILITY
# =============================================================================
def _is_entry_point(file_path: str) -> bool:
"""Check if file is an entry point: apps/{name}.py (directly in apps/, not subdirectory)."""
p = Path(file_path)
if not p.name.endswith('.py'):
return False
if 'apps/' not in file_path:
return False
return p.parent.name == 'apps'
def _is_applicable(checker, file_path: str) -> bool:
"""Determine if a checker applies to the given file.
Rules based on AUDIT_SCOPE:
- "entry_point" (default) -> only apps/{name}.py files
- "all_files" -> any .py file
- "branch_level" -> not applicable to single-file checks
"""
scope = getattr(checker, "AUDIT_SCOPE", "entry_point")
# Branch-level checkers need a branch path, not a single file
if scope == "branch_level":
return False
# Only check_module() capable checkers
if not hasattr(checker, "check_module"):
return False
if scope == "all_files":
return file_path.endswith('.py')
# Default: entry_point scope
return _is_entry_point(file_path)
# =============================================================================
# CORE LOGIC
# =============================================================================
def run_checklist(file_path: str, pack_name: str = "aipass") -> List[Dict]:
"""Run applicable standards checkers against a single file.
Args:
file_path: Absolute path to the file to check.
pack_name: Checker pack to use (default: "aipass").
Returns:
List of result dicts: [{"standard": str, "passed": bool, "detail": str|None}]
"""
resolved = str(Path(file_path).resolve())
if not Path(resolved).exists():
return [{"standard": "(error)", "passed": False, "detail": f"File not found: {resolved}"}]
if not resolved.endswith('.py'):
return [{"standard": "(skip)", "passed": True, "detail": "Not a Python file"}]
# Discover pack path
pack_path = _resolve_pack_path(pack_name)
if pack_path is None:
return [{"standard": "(error)", "passed": False, "detail": f"Pack '{pack_name}' not found"}]
# Load checkers
checkers = discover_checkers(pack_path)
if not checkers:
return [{"standard": "(error)", "passed": False, "detail": "No checkers discovered"}]
# Resolve branch and bypass rules
bypass_rules = _load_bypass_for_file(resolved)
# Run applicable checkers
results = []
for name, checker in sorted(checkers.items()):
if not _is_applicable(checker, resolved):
continue
try:
r = checker.check_module(resolved, bypass_rules=bypass_rules) # type: ignore[attr-defined]
except Exception as e:
results.append({"standard": name, "passed": False, "detail": f"Checker error: {e}"})
continue
passed = r.get("passed", True)
detail = None
if not passed:
# Extract concise failure detail from checks
detail = _format_failure(r)
results.append({"standard": name, "passed": passed, "detail": detail})
if not results:
return [{"standard": "(skip)", "passed": True, "detail": "No applicable checkers for this file"}]
return results
def _resolve_pack_path(pack_name: str) -> Optional[Path]:
"""Resolve pack name to its directory path."""
handlers_dir = Path(__file__).parent.parent / "handlers"
candidate = handlers_dir / f"{pack_name}_standards"
if candidate.is_dir() and list(candidate.glob("*_check.py")):
return candidate
return None
def _load_bypass_for_file(file_path: str) -> list:
"""Load bypass rules for the branch containing file_path."""
branch = get_branch_from_path(file_path)
if branch is None:
return []
branch_path = branch.get("path", "")
if not branch_path:
return []
return load_bypass_rules(branch_path)
def _format_failure(result: Dict) -> str:
"""Extract a concise one-line failure description from checker result."""
checks = result.get("checks", [])
failed = [c for c in checks if not c.get("passed", False)]
if not failed:
return "Failed (no details)"
# Use first failure's message, trimmed
msg = failed[0].get("message", "Unknown issue")
# If multiple failures, indicate count
if len(failed) > 1:
msg = f"{msg} (+{len(failed) - 1} more)"
return msg
# =============================================================================
# OUTPUT FORMATTING
# =============================================================================
def _print_results(results: List[Dict], file_path: str) -> None:
"""Print concise pass/fail output for hook consumption."""
p = Path(file_path)
console.print(f"[dim]{p.name}[/dim]")
all_passed = True
for r in results:
std = r["standard"]
if r["passed"]:
console.print(f" [green]\u2713[/green] {std}")
else:
all_passed = False
detail = r.get("detail", "")
if detail:
console.print(f" [red]\u2717[/red] {std}: {detail}")
else:
console.print(f" [red]\u2717[/red] {std}")
if all_passed:
console.print(f"[green]All {len(results)} standards passed[/green]")
# =============================================================================
# COMMAND HANDLER
# =============================================================================
def handle_command(command: str, args: List[str]) -> bool:
"""
Handle 'checklist' command — per-file standards check.
Args:
command: Command name
args: Additional arguments
[] -> print_introspection() (standard no-args gate)
["--help"] -> print_help()
["<file>"] -> run checklist on file
["--pack", "<pack>", "<file>"] -> run with specific pack
Returns:
True if handled, False if not this module's command
"""
if command != "checklist":
return False
# No args -> introspection
if not args:
print_introspection()
return True
# --help
if args[0] in ["--help", "-h", "help"]:
print_help()
return True
# Parse arguments
pack_name = "aipass"
file_path = None
i = 0
while i < len(args):
if args[i] in ("--pack", "-p") and i + 1 < len(args):
pack_name = args[i + 1]
i += 2
elif not args[i].startswith("-"):
file_path = args[i]
i += 1
else:
i += 1
if file_path is None:
error("No file specified", suggestion="Usage: drone @seedgo checklist <file>")
return True
# Resolve path
resolved = Path(file_path)
if not resolved.is_absolute():
resolved = Path.cwd() / resolved
resolved = resolved.resolve()
# Run checklist
results = run_checklist(str(resolved), pack_name=pack_name)
# Print results
_print_results(results, str(resolved))
return True
# =============================================================================
# INTROSPECTION & HELP
# =============================================================================
def print_introspection() -> None:
"""Display module info and connected handlers."""
console.print()
console.print("[bold cyan]checklist Module[/bold cyan]")
console.print("Per-file standards check — quick pass/fail for hook consumption")
console.print()
# Show discovered packs
handlers_dir = Path(__file__).parent.parent / "handlers"
packs = {}
if handlers_dir.exists():
for d in sorted(handlers_dir.iterdir()):
if d.is_dir() and d.name.endswith("_standards"):
check_files = list(d.glob("*_check.py"))
if check_files:
packs[d.name.removesuffix("_standards")] = len(check_files)
console.print("[yellow]Discovered Packs:[/yellow]")
for name, count in packs.items():
console.print(f" [cyan]{name}[/cyan] ({count} checker{'s' if count != 1 else ''})")
if not packs:
console.print(" [dim]No packs found[/dim]")
console.print()
console.print("[yellow]Connected Handlers:[/yellow]")
console.print(" [cyan]handlers/audit/[/cyan]")
console.print(" [dim]- branch_audit.py (discover_checkers — dynamic checker loading)[/dim]")
console.print()
console.print(" [cyan]handlers/bypass/[/cyan]")
console.print(" [dim]- bypass_handler.py (get_branch_from_path, load_bypass_rules)[/dim]")
console.print()
console.print("[yellow]External Dependencies:[/yellow]")
console.print(" [dim]- aipass.prax (logger)[/dim]")
console.print(" [dim]- aipass.cli (console)[/dim]")
console.print()
console.print("[yellow]Next:[/yellow]")
console.print(" [green]drone @seedgo checklist <file>[/green] [dim]# Check a single file[/dim]")
console.print(" [green]drone @seedgo checklist --help[/green] [dim]# Full usage guide[/dim]")
console.print()
def print_help() -> None:
"""Print help information."""
console.print()
console.print("[bold cyan]Per-File Standards Checklist[/bold cyan]")
console.print("Quick pass/fail check for a single file against applicable standards")
console.print()
console.print("[yellow]USAGE:[/yellow]")
console.print(" [green]drone @seedgo checklist <file>[/green] [dim]# Check file (aipass pack)[/dim]")
console.print(" [green]drone @seedgo checklist --pack <pack> <file>[/green] [dim]# Check with specific pack[/dim]")
console.print(" [green]drone @seedgo checklist --help[/green] [dim]# This help message[/dim]")
console.print()
console.print("[yellow]OUTPUT FORMAT:[/yellow]")
console.print(" [green]\u2713[/green] standard_name [dim]# Passed[/dim]")
console.print(" [red]\u2717[/red] standard_name: failure detail [dim]# Failed with reason[/dim]")
console.print()
console.print("[yellow]SCOPE RULES:[/yellow]")
console.print(" Checkers with [cyan]AUDIT_SCOPE = \"entry_point\"[/cyan] only run on apps/{name}.py files")
console.print(" Checkers with [cyan]AUDIT_SCOPE = \"all_files\"[/cyan] run on any .py file")
console.print(" Checkers with [cyan]AUDIT_SCOPE = \"branch_level\"[/cyan] are skipped (need full branch)")
console.print()
console.print("[yellow]EXAMPLES:[/yellow]")
console.print(" [dim]# Check a module file (only all_files checkers apply)[/dim]")
console.print(" [green]drone @seedgo checklist src/aipass/flow/apps/modules/step_runner.py[/green]")
console.print()
console.print(" [dim]# Check an entry point (all checkers apply)[/dim]")
console.print(" [green]drone @seedgo checklist src/aipass/flow/apps/flow.py[/green]")
console.print()
console.print("[yellow]REFERENCE:[/yellow]")
console.print(" Runs after every file edit via hook. Bypass rules from .seedgo/bypass.json are respected.")
console.print(" For full branch audit, use: [green]drone @seedgo audit aipass[/green]")
console.print()
# =============================================================================
# STANDALONE EXECUTION
# =============================================================================
if __name__ == "__main__":
# Handle help flag
if len(sys.argv) > 1 and sys.argv[1] in ['--help', '-h', 'help']:
print_help()
sys.exit(0)
# Confirm Prax logger connection
logger.info("Prax logger connected to checklist")
# No args -> introspection
if len(sys.argv) < 2:
print_introspection()
sys.exit(0)
# Run checklist
handle_command("checklist", sys.argv[1:])
+7 -1
View File
@@ -135,6 +135,7 @@ def print_introspection() -> None:
console.print("[yellow]Next:[/yellow] Explore a module")
console.print(" [green]drone @seedgo standards_query[/green] [dim]# Browse standards content[/dim]")
console.print(" [green]drone @seedgo audit aipass[/green] [dim]# Run compliance audit[/dim]")
console.print(" [green]drone @seedgo checklist <file>[/green] [dim]# Per-standard checklist on a file[/dim]")
console.print(" [green]drone @seedgo --help[/green] [dim]# Full usage guide[/dim]")
console.print()
@@ -191,11 +192,16 @@ def print_help() -> None:
console.print(" [green]drone @seedgo standards_query aipass_standards cli[/green] [dim]# Show standard content[/dim]")
console.print()
console.print("[yellow]Checklist:[/yellow]")
console.print(" [green]drone @seedgo checklist[/green] [dim]# Show checklist introspection[/dim]")
console.print(" [green]drone @seedgo checklist <file>[/green] [dim]# Run per-standard checklist on file[/dim]")
console.print()
console.print("─" * 70)
console.print()
# Commands line for drone discovery
console.print("[dim]Commands: audit, standards_audit, standards_query, diagnostics, diagnostics_audit, readme, readme_update, --help[/dim]")
console.print("[dim]Commands: audit, standards_audit, standards_query, checklist, diagnostics, diagnostics_audit, readme, readme_update, --help[/dim]")
console.print()
+6 -1
View File
@@ -120,11 +120,16 @@ def get_help(command: str | None = None) -> str:
lines.append(" [dim]drone @seedgo standards_query aipass_standards cli # Show content[/dim]")
lines.append("")
lines.append("[yellow]Checklist:[/yellow]")
lines.append(" [dim]drone @seedgo checklist # Show checklist introspection[/dim]")
lines.append(" [dim]drone @seedgo checklist <file> # Run per-standard checklist on file[/dim]")
lines.append("")
lines.append("─" * 70)
lines.append("")
# Commands line for drone discovery
lines.append("[dim]Commands: audit, standards_audit, standards_query, diagnostics, diagnostics_audit, readme, readme_update, --help[/dim]")
lines.append("[dim]Commands: audit, standards_audit, standards_query, checklist, diagnostics, diagnostics_audit, readme, readme_update, --help[/dim]")
lines.append("")
return "\n".join(lines)
+134
View File
@@ -0,0 +1,134 @@
"""Spawn handlers package - Security protected."""
import inspect
from pathlib import Path
MY_BRANCH = "aipass.spawn"
def _find_real_caller():
"""
Walk the stack to find the actual file that triggered this import.
Skips:
- This file (handlers/__init__.py)
- Python's importlib internals
- Frozen modules
Returns tuple: (file_path, import_line) or (None, None)
"""
stack = inspect.stack()
this_file = str(Path(__file__).resolve())
for frame_info in stack:
filename = frame_info.filename
# Skip this file
if this_file in str(Path(filename).resolve()):
continue
# Skip Python internals
if filename.startswith("<") or "importlib" in filename:
continue
# Found a real file - try to get the import line
import_line = None
if frame_info.code_context:
import_line = frame_info.code_context[0].strip()
return str(Path(filename).resolve()), import_line
return None, None
def _extract_branch_name(filepath: str) -> str:
"""Extract branch name from a file path."""
parts = Path(filepath).parts
for i, part in enumerate(parts):
if part == "aipass":
if i + 1 < len(parts):
return parts[i + 1]
return "unknown"
def _guard_branch_access():
"""
Block cross-branch handler imports.
Only code from within the 'spawn' branch can import these handlers.
External branches must use aipass.spawn.apps.modules instead.
"""
caller_file, import_line = _find_real_caller()
# DEBUG: Print what we found
import os
if os.environ.get("AIPASS_DEBUG_GUARD"):
import sys
print(f"[GUARD DEBUG] caller_file = {caller_file}", file=sys.stderr)
print(f"[GUARD DEBUG] import_line = {import_line}", file=sys.stderr)
if caller_file is None:
# Can't determine caller from real files
# Check if we're being run from command line (external)
# by looking at the raw stack for <string> or <stdin>
stack = inspect.stack()
for frame in stack:
if frame.filename in ("<string>", "<stdin>"):
# Try to get the import line from the frame
target_line = "unknown"
if frame.code_context:
target_line = frame.code_context[0].strip()
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller: interactive/script\n"
f" Blocked: {target_line}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
return # Allow if truly can't determine
# Check if caller is from our branch
# MY_BRANCH is "aipass.spawn" (dotted), but filesystem uses "/aipass/spawn/"
branch_path = "/" + MY_BRANCH.replace(".", "/") + "/"
if branch_path in caller_file:
return # Same branch, allowed
# External caller - block access
caller_branch = _extract_branch_name(caller_file)
caller_filename = Path(caller_file).name
blocked_import = import_line if import_line else "unknown"
raise ImportError(
f"\n{'='*60}\n"
f"ACCESS DENIED: Cross-branch handler import blocked\n"
f"{'='*60}\n"
f" Caller branch: {caller_branch}\n"
f" Caller file: {caller_filename}\n"
f" Blocked: {blocked_import}\n"
f"\n"
f" Handlers are internal to their branch.\n"
f" Use the module API instead:\n"
f" from {MY_BRANCH}.apps.modules.<module> import <function>\n"
f"\n"
f" Example:\n"
f" from {MY_BRANCH}.apps.modules.logger import logger\n"
f"\n"
f" For full standards guide:\n"
f" drone @seed handlers\n"
f"{'='*60}"
)
# Run guard at import time
_guard_branch_access()