diff --git a/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py b/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py index 2f832c1b..8db59ec5 100644 --- a/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py +++ b/src/aipass/ai_mail/apps/handlers/dispatch/daemon.py @@ -201,28 +201,45 @@ def is_kill_switch_active(config: Dict[str, Any]) -> bool: def _write_pid_file() -> bool: - """Write current PID to daemon.pid. Returns False if another daemon is running.""" - if DAEMON_PID_FILE.exists(): - try: - old_pid = int(DAEMON_PID_FILE.read_text().strip()) - try: - os.kill(old_pid, 0) - # Process exists — another daemon is running - logger.info(f"Another daemon already running (PID {old_pid}). Exiting.") - return False - except ProcessLookupError: - # Stale PID file — process is dead, we can take over - logger.info(f"Removing stale PID file (PID {old_pid} is dead)") - except PermissionError: - # Process exists but we can't signal it - logger.info(f"Another daemon already running (PID {old_pid}, permission denied). Exiting.") - return False - except (ValueError, OSError): - logger.info("Corrupt PID file — removing") - + """Write current PID to daemon.pid atomically. Returns False if another daemon is running.""" DAEMON_PID_FILE.parent.mkdir(parents=True, exist_ok=True) - DAEMON_PID_FILE.write_text(str(os.getpid())) - return True + try: + fd = os.open(str(DAEMON_PID_FILE), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) + try: + os.write(fd, str(os.getpid()).encode("utf-8")) + finally: + os.close(fd) + return True + except FileExistsError: + logger.info("[daemon] PID file already exists, checking owner") + + # PID file exists — check if the owning process is alive + try: + old_pid = int(DAEMON_PID_FILE.read_text().strip()) + try: + os.kill(old_pid, 0) + logger.info(f"Another daemon already running (PID {old_pid}). Exiting.") + return False + except ProcessLookupError: + logger.info(f"Removing stale PID file (PID {old_pid} is dead)") + except PermissionError: + logger.info(f"Another daemon already running (PID {old_pid}, permission denied). Exiting.") + return False + except (ValueError, OSError): + logger.info("Corrupt PID file — removing") + + # Stale or corrupt — remove and retry atomically + DAEMON_PID_FILE.unlink(missing_ok=True) + try: + fd = os.open(str(DAEMON_PID_FILE), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644) + try: + os.write(fd, str(os.getpid()).encode("utf-8")) + finally: + os.close(fd) + return True + except FileExistsError: + logger.info("Another daemon raced us for the PID file. Exiting.") + return False def _remove_pid_file() -> None: @@ -245,6 +262,17 @@ def get_registered_branches() -> list: return data.get("branches", []) +def _is_registered_sender(sender: str) -> bool: + """Check if sender email exists in the branch registry (DPLAN-0159 S2).""" + registry = _read_json(BRANCH_REGISTRY) + if registry is None: + return True # fail open if registry unreadable + for branch in registry.get("branches", []): + if branch.get("email") == sender: + return True + return False + + def check_inbox_for_dispatch(branch_path: Path) -> Optional[Dict[str, Any]]: """ Check a branch's inbox for unprocessed --dispatch emails. @@ -311,6 +339,10 @@ def spawn_agent( subject = message.get("subject", "") max_turns = config.get("max_turns_per_wake", 100) + if message.get("auto_execute") and not _is_registered_sender(sender): + logger.warning("[daemon] Dispatch from unregistered sender %s — rejecting", sender) + return False + lock_file_path = str(branch_path / ".ai_mail.local" / ".dispatch.lock") # Prompt — only interpolate system-generated metadata (id, sender email). diff --git a/src/aipass/ai_mail/apps/handlers/email/reply.py b/src/aipass/ai_mail/apps/handlers/email/reply.py index 2d27aa85..44d35eef 100644 --- a/src/aipass/ai_mail/apps/handlers/email/reply.py +++ b/src/aipass/ai_mail/apps/handlers/email/reply.py @@ -164,6 +164,30 @@ def send_reply(from_branch_path: Path, original_email: Dict, reply_message: str) return True, f"Reply sent to {reply_destination}, original closed", reply_id +def _validate_reply_path(reply_path: str) -> Tuple[bool, str]: + """Validate that reply_path points to a legitimate inbox.json. + + Checks: (a) path resolves, (b) ends with .ai_mail.local/inbox.json, + (c) an AIPASS_REGISTRY.json exists in an ancestor directory. + """ + try: + path = Path(reply_path).resolve() + except (OSError, ValueError) as e: + logger.warning("[reply] _validate_reply_path resolution failed: %s", e) + return False, f"Path resolution failed: {e}" + + if path.name != "inbox.json" or path.parent.name != ".ai_mail.local": + return False, f"Path does not end with .ai_mail.local/inbox.json: {path}" + + for parent in path.parents: + if (parent / "AIPASS_REGISTRY.json").exists(): + return True, "" + if parent == parent.parent: + break + + return False, f"No AIPASS_REGISTRY.json found in ancestors of {path}" + + def _deliver_via_reply_path( reply_path: str, reply_email_data: Dict, @@ -185,7 +209,12 @@ def _deliver_via_reply_path( Returns: Tuple of (success, message, reply_id or None) """ - inbox_file = Path(reply_path) + valid, reason = _validate_reply_path(reply_path) + if not valid: + logger.warning("[reply] reply_path rejected: %s", reason) + return False, f"Invalid reply_path: {reason}", None + + inbox_file = Path(reply_path).resolve() success, error_msg, reply_id = deliver_to_inbox_file(inbox_file, reply_email_data) if not success: logger.warning("[reply] _deliver_via_reply_path failed for %s: %s", reply_path, error_msg) diff --git a/src/aipass/ai_mail/tests/test_daemon.py b/src/aipass/ai_mail/tests/test_daemon.py index a97d3bc5..8478667b 100644 --- a/src/aipass/ai_mail/tests/test_daemon.py +++ b/src/aipass/ai_mail/tests/test_daemon.py @@ -773,6 +773,7 @@ from aipass.ai_mail.apps.handlers.dispatch.daemon import ( _handle_signal, _check_lock, _acquire_lock, + _is_registered_sender, poll_cycle, _write_pid_file, _remove_pid_file, @@ -1773,3 +1774,170 @@ def test_poll_cycle_spawn_failure_not_counted(tmp_path, monkeypatch): result = poll_cycle(config, state) assert result == 0 + + +# ---- _is_registered_sender tests (DPLAN-0159 S2) ---------------- + + +def test_is_registered_sender_found(tmp_path, monkeypatch): + """Registered sender returns True.""" + registry = {"branches": [{"email": "@flow"}, {"email": "@backup"}]} + reg_file = tmp_path / "AIPASS_REGISTRY.json" + reg_file.write_text(json.dumps(registry), encoding="utf-8") + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", reg_file) + + assert _is_registered_sender("@flow") is True + + +def test_is_registered_sender_not_found(tmp_path, monkeypatch): + """Unregistered sender returns False.""" + registry = {"branches": [{"email": "@flow"}]} + reg_file = tmp_path / "AIPASS_REGISTRY.json" + reg_file.write_text(json.dumps(registry), encoding="utf-8") + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", reg_file) + + assert _is_registered_sender("@evil") is False + + +def test_is_registered_sender_missing_registry(tmp_path, monkeypatch): + """Missing registry fails open (returns True).""" + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", tmp_path / "missing.json") + + assert _is_registered_sender("@anyone") is True + + +def test_is_registered_sender_empty_branches(tmp_path, monkeypatch): + """Empty branches list returns False for any sender.""" + registry = {"branches": []} + reg_file = tmp_path / "AIPASS_REGISTRY.json" + reg_file.write_text(json.dumps(registry), encoding="utf-8") + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", reg_file) + + assert _is_registered_sender("@flow") is False + + +# ---- spawn_agent sender auth tests (DPLAN-0159 S2) ---------------- + + +def test_spawn_agent_rejects_unregistered_auto_execute(tmp_path, monkeypatch): + """Auto-execute dispatch from unregistered sender is rejected.""" + branch_path = tmp_path / "branch" + branch_path.mkdir() + + registry = {"branches": [{"email": "@flow"}]} + reg_file = tmp_path / "AIPASS_REGISTRY.json" + reg_file.write_text(json.dumps(registry), encoding="utf-8") + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", reg_file) + + message = {"from": "@forged", "id": "msg1", "subject": "Fake", "auto_execute": True} + config = {"max_turns_per_wake": 50} + state = {"daily_counts": {}, "session_cycles": {}} + + result = spawn_agent(branch_path, "@testbranch", message, config, state) + + assert result is False + + +def test_spawn_agent_allows_registered_auto_execute(tmp_path, monkeypatch): + """Auto-execute dispatch from registered sender proceeds to spawn.""" + branch_path = tmp_path / "branch" + branch_path.mkdir() + (branch_path / "logs").mkdir() + + registry = {"branches": [{"email": "@devpulse"}, {"email": "@flow"}]} + reg_file = tmp_path / "AIPASS_REGISTRY.json" + reg_file.write_text(json.dumps(registry), encoding="utf-8") + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", reg_file) + + message = {"from": "@devpulse", "id": "msg1", "subject": "Task", "auto_execute": True} + config = {"max_turns_per_wake": 50} + state = {"daily_counts": {}, "session_cycles": {}} + + mock_process = MagicMock() + mock_process.pid = 54321 + + with ( + patch( + "aipass.ai_mail.apps.handlers.dispatch.daemon.subprocess.Popen", + return_value=mock_process, + ), + patch( + "aipass.ai_mail.apps.handlers.dispatch.daemon._acquire_lock", + return_value=(True, "Lock acquired"), + ), + patch("aipass.ai_mail.apps.handlers.dispatch.daemon.log_dispatch"), + patch( + "aipass.ai_mail.apps.handlers.dispatch.daemon.send_notification", + create=True, + ), + ): + result = spawn_agent(branch_path, "@testbranch", message, config, state) + + assert result is True + + +def test_spawn_agent_no_auth_check_without_auto_execute(tmp_path, monkeypatch): + """Non-auto_execute email skips sender auth check.""" + branch_path = tmp_path / "branch" + branch_path.mkdir() + (branch_path / "logs").mkdir() + + registry = {"branches": []} + reg_file = tmp_path / "AIPASS_REGISTRY.json" + reg_file.write_text(json.dumps(registry), encoding="utf-8") + monkeypatch.setattr(daemon_mod, "BRANCH_REGISTRY", reg_file) + + message = {"from": "@unknown", "id": "msg1", "subject": "Manual"} + config = {"max_turns_per_wake": 50} + state = {"daily_counts": {}, "session_cycles": {}} + + mock_process = MagicMock() + mock_process.pid = 54321 + + with ( + patch( + "aipass.ai_mail.apps.handlers.dispatch.daemon.subprocess.Popen", + return_value=mock_process, + ), + patch( + "aipass.ai_mail.apps.handlers.dispatch.daemon._acquire_lock", + return_value=(True, "Lock acquired"), + ), + patch("aipass.ai_mail.apps.handlers.dispatch.daemon.log_dispatch"), + patch( + "aipass.ai_mail.apps.handlers.dispatch.daemon.send_notification", + create=True, + ), + ): + result = spawn_agent(branch_path, "@testbranch", message, config, state) + + assert result is True + + +# ---- _write_pid_file atomic tests (DPLAN-0159 S5) ---------------- + + +def test_write_pid_file_atomic_no_existing(tmp_path, monkeypatch): + """Atomic creation succeeds when no PID file exists.""" + pid_file = tmp_path / "daemon.pid" + monkeypatch.setattr(daemon_mod, "DAEMON_PID_FILE", pid_file) + + result = _write_pid_file() + + assert result is True + assert pid_file.exists() + assert int(pid_file.read_text().strip()) == os.getpid() + + +def test_write_pid_file_atomic_race_second_loses(tmp_path, monkeypatch): + """Second daemon loses the race when both try O_CREAT|O_EXCL.""" + pid_file = tmp_path / "daemon.pid" + monkeypatch.setattr(daemon_mod, "DAEMON_PID_FILE", pid_file) + + # First daemon wins + pid_file.write_text(str(os.getpid()), encoding="utf-8") + + # Second daemon: file exists, owner is alive → returns False + result = _write_pid_file() + + assert result is False diff --git a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py index aa6a62cc..8fdbfdc3 100644 --- a/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py +++ b/src/aipass/prax/apps/handlers/monitoring/filesystem_handler.py @@ -130,8 +130,20 @@ class MonitoringFileHandler(FileSystemEventHandler): return f"⚡ Bash: {desc[:120]}" if tool_name in ("Grep", "Glob"): return f"🔍 {tool_name}: {inp.get('pattern', '')[:80]}" - if tool_name == "Task": + if tool_name in ("Task", "Agent"): return f"🚀 Agent: {inp.get('description', '')[:80]}" + if tool_name == "WebFetch": + return f"🌐 WebFetch: {inp.get('url', '')[:80]}" + if tool_name == "WebSearch": + return f"🔍 WebSearch: {inp.get('query', '')[:80]}" + if tool_name == "Monitor": + return f"⚡ Monitor: {inp.get('command', '')[:120]}" + if tool_name == "Skill": + return f"🎯 Skill: {inp.get('skill', '')[:80]}" + if tool_name == "NotebookEdit": + fp = inp.get("notebook_path", "") + short = fp.split("/")[-1] if "/" in fp else fp + return f"🔧 NotebookEdit: {short}" return f"🔧 {tool_name}" @staticmethod diff --git a/src/aipass/seedgo/.seedgo/bypass.json b/src/aipass/seedgo/.seedgo/bypass.json index 78aa9f0c..736e2a07 100644 --- a/src/aipass/seedgo/.seedgo/bypass.json +++ b/src/aipass/seedgo/.seedgo/bypass.json @@ -81,6 +81,11 @@ ], "reason": "Same-branch cross-handler import (allowed per architecture standard). Diagnostics handler imports bypass/ignore_handler for audit ignore patterns." }, + { + "file": "apps/handlers/aipass_standards/", + "standard": "handlers", + "reason": "All 33 checkers import is_bypassed from bypass/utils.py — shared utility extracted from 33 duplicate copies (DPLAN-0159 A2). Same-branch cross-handler import, intentional." + }, { "file": "apps/handlers/aipass_standards/cli_check.py", "standard": "debug_print", diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/architecture_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/architecture_check.py index 1b97bd99..71c705c8 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/architecture_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/architecture_check.py @@ -21,6 +21,7 @@ from typing import Dict, List, Optional from aipass.seedgo.apps.handlers.bypass.ignore_handler import get_template_ignore_patterns from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files AUDIT_SCOPE = "all_files" @@ -33,26 +34,6 @@ _SRC_PKG_ROOT = PACK_ROOT.parent.parent # apps/ -> seedgo/ -> src/aipass/ SPAWN_TEMPLATES_DIR = _SRC_PKG_ROOT / "spawn" / "templates" -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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows architecture standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/cli_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/cli_check.py index 3ed909a7..84c76897 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/cli_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/cli_check.py @@ -19,31 +19,12 @@ from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows CLI standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/cli_flags_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/cli_flags_check.py index 5ed60fb3..1555cb5c 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/cli_flags_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/cli_flags_check.py @@ -27,28 +27,12 @@ from typing import Dict, List from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: entry points only (apps/{name}.py) AUDIT_SCOPE = "entry_point" -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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows CLI flags standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/commented_logger_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/commented_logger_check.py index d1d870d1..53878939 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/commented_logger_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/commented_logger_check.py @@ -28,6 +28,7 @@ from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: scan every .py file, not just entry point AUDIT_SCOPE = "all_files" @@ -36,23 +37,6 @@ AUDIT_SCOPE = "all_files" _COMMENTED_LOGGER_RE = re.compile(r"#\s*logger\.(error|warning|warn|info|exception|critical|debug)\s*\(") -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check a Python file for commented-out logger calls. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/dead_code_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/dead_code_check.py index 6ae020e6..0e521676 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/dead_code_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/dead_code_check.py @@ -23,6 +23,7 @@ from pathlib import Path from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "branch_level" @@ -58,23 +59,6 @@ _SKIP_DIRS = { # ============================================= -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # ============================================= # FILE COLLECTION # ============================================= diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py index c4e44abf..0d260f1e 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/debug_print_check.py @@ -20,6 +20,7 @@ from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "all_files" @@ -34,26 +35,6 @@ _DOCTEST_RE = re.compile(r"^\s*(\.\.\.|>>>)\s") _TEST_FILE_RE = re.compile(r"^(test_.+|.+_test|conftest)\.py$") -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def _is_in_main_block(lines: list[str], lineno: int) -> bool: """ Return True if the line at *lineno* (1-based) is inside an diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/deep_nesting_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/deep_nesting_check.py index 5dc0e7a2..4b4848b2 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/deep_nesting_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/deep_nesting_check.py @@ -22,6 +22,7 @@ from pathlib import Path from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "all_files" @@ -35,23 +36,6 @@ DEPTH_LIMIT = 4 # -- Bypass helper ----------------------------------------------------------- -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # -- AST depth analysis ------------------------------------------------------ diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/documentation_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/documentation_check.py index cbdaff5c..195deaa1 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/documentation_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/documentation_check.py @@ -19,28 +19,12 @@ from typing import Dict, List from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows documentation standards. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/encapsulation_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/encapsulation_check.py index dc53c3a1..5ee62e43 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/encapsulation_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/encapsulation_check.py @@ -23,6 +23,7 @@ from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files AUDIT_SCOPE = "all_files" @@ -38,26 +39,6 @@ def _find_registry() -> Path: return Path.cwd() / "AIPASS_REGISTRY.json" -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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def get_branch_from_path(file_path: str) -> Optional[Dict]: """Detect which branch a file belongs to using AIPASS_REGISTRY.json.""" try: diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/error_handling_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/error_handling_check.py index 4a3be8be..ee5787c6 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/error_handling_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/error_handling_check.py @@ -17,28 +17,12 @@ from pathlib import Path from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """Check if module follows error handling standards""" checks = [] diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/handler_import_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/handler_import_check.py index 554af876..f5657d2f 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/handler_import_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/handler_import_check.py @@ -20,6 +20,7 @@ from pathlib import Path from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "branch_level" @@ -29,28 +30,6 @@ AUDIT_SCOPE = "branch_level" # ============================================= -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # ============================================= # BRANCH-LEVEL CHECK (audit pipeline entry) # ============================================= diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/handlers_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/handlers_check.py index 60c6c49d..34bcadf8 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/handlers_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/handlers_check.py @@ -19,31 +19,12 @@ from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if handler follows handler standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/hardcoded_key_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/hardcoded_key_check.py index 9c424188..7d390a3e 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/hardcoded_key_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/hardcoded_key_check.py @@ -20,6 +20,7 @@ from pathlib import Path from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "all_files" @@ -98,23 +99,6 @@ _PAT_REGEX_CONTEXT = re.compile(r"""re\.compile|r["']|\\[dws\^]""") # -- Helpers ---------------------------------------------------------------- -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def _is_placeholder(key_value: str) -> bool: """Return True if the captured key value looks like a placeholder.""" if _PLACEHOLDER_VALUE_RE.search(key_value): diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/help_text_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/help_text_check.py index 41338217..e6750dad 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/help_text_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/help_text_check.py @@ -26,6 +26,7 @@ from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "all_files" @@ -68,23 +69,6 @@ def _line_has_python_instruction(line: str) -> bool: # ── Bypass helper ─────────────────────────────────────────────────────── -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # ── Main checker entry point ──────────────────────────────────────────── diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/imports_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/imports_check.py index 82e19edd..719f680e 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/imports_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/imports_check.py @@ -20,28 +20,12 @@ from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows import standards for pip packages. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/introspection_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/introspection_check.py index 1d5e1f56..711ecd87 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/introspection_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/introspection_check.py @@ -23,31 +23,12 @@ from pathlib import Path from typing import Dict, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows introspection standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/json_structure_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/json_structure_check.py index 438ccce7..c78d79b8 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/json_structure_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/json_structure_check.py @@ -26,28 +26,12 @@ from pathlib import Path from typing import Dict, List from aipass.prax import logger +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows JSON structure standards. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/log_handler_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/log_handler_check.py index 428965a3..9d6d1909 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/log_handler_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/log_handler_check.py @@ -25,28 +25,12 @@ from pathlib import Path from typing import Dict, List from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows log handler standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/log_level_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/log_level_check.py index 98a9be78..6991a26a 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/log_level_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/log_level_check.py @@ -24,29 +24,13 @@ import re from pathlib import Path from typing import Dict, List, Optional from aipass.prax import logger +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed from aipass.seedgo.apps.handlers.json import json_handler # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows log level hygiene standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/log_structure_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/log_structure_check.py index ff6c7adb..6d5b78ae 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/log_structure_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/log_structure_check.py @@ -21,6 +21,7 @@ from pathlib import Path from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files AUDIT_SCOPE = "all_files" @@ -50,23 +51,6 @@ def _find_branch_root(file_path: Path) -> Path: return file_path.parent -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check module logging structure against the two-tier model. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/log_visibility_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/log_visibility_check.py index 03d56bc8..6713de7e 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/log_visibility_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/log_visibility_check.py @@ -25,6 +25,7 @@ from pathlib import Path from typing import Dict, List from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files AUDIT_SCOPE = "all_files" @@ -34,23 +35,6 @@ _GETLOGGER_PAT = r"logging" + r"\.getLogger\s*\(" _FILEHANDLER_PAT = r"logging" + r"\.FileHandler\s*\(" -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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module's logging is visible to Prax monitor (system_logs/). diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/meta_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/meta_check.py index 5fc0d1a0..c9da5c32 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/meta_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/meta_check.py @@ -31,6 +31,7 @@ from pathlib import Path from typing import Dict, List from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "all_files" @@ -50,23 +51,6 @@ REQUIRED_FIELDS = { } -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module has a valid library-profile META block. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/modules_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/modules_check.py index 2aa18052..2b7bc906 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/modules_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/modules_check.py @@ -19,31 +19,12 @@ from pathlib import Path from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows module standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/naming_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/naming_check.py index c8c3831a..9c535c01 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/naming_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/naming_check.py @@ -18,31 +18,12 @@ from pathlib import Path from typing import Dict, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module follows naming standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/permission_flags_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/permission_flags_check.py index 1b98959e..a12a989a 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/permission_flags_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/permission_flags_check.py @@ -26,28 +26,12 @@ from pathlib import Path from typing import Dict, List from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: all Python files 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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def _get_non_code_lines(lines: List[str]) -> set: """ Build a set of line numbers that are inside docstrings or comments. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/readme_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/readme_check.py index 9a250310..932c9dc0 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/readme_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/readme_check.py @@ -27,28 +27,12 @@ from pathlib import Path from typing import Dict, List, Optional from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: entry points only (apps/{name}.py) AUDIT_SCOPE = "entry_point" -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""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if branch README follows standards diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/ruff_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/ruff_check.py index b127be53..496b2b7a 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/ruff_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/ruff_check.py @@ -31,25 +31,12 @@ from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "branch_level" ADVISORY = True -def is_bypassed(file_path: str, standard: str, bypass_rules: list | None = None) -> bool: - """Check if a violation should be bypassed via standard bypass.json rules.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - return True - return False - - def _load_ruff_bypass(branch_path: Path) -> list: """Load .seedgo/ruff_bypass.json for ruff-specific bypass rules.""" bypass_file = branch_path / ".seedgo" / "ruff_bypass.json" diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/shebang_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/shebang_check.py index 56e6688b..d50d0b70 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/shebang_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/shebang_check.py @@ -19,28 +19,12 @@ from pathlib import Path from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # 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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if a Python file contains a shebang line. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/silent_catch_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/silent_catch_check.py index 92230cdf..62f055e2 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/silent_catch_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/silent_catch_check.py @@ -26,6 +26,7 @@ from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed # Audit scope: scan every .py file, not just entry point AUDIT_SCOPE = "all_files" @@ -34,23 +35,6 @@ AUDIT_SCOPE = "all_files" _LOGGING_ATTRS = frozenset({"error", "warning", "warn", "info", "debug", "exception", "critical"}) -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # -- AST helpers (extracted from devpulse silent_catch_scanner_v2) --------- diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py index 96950529..fa733b90 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/stderr_routing_check.py @@ -27,29 +27,13 @@ import re from pathlib import Path from typing import Dict, List from aipass.prax import logger +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed from aipass.seedgo.apps.handlers.json import json_handler 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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check if module routes error/warning output to stderr via CLI display functions. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/test_quality_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/test_quality_check.py index 6921ee7a..2771ba42 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/test_quality_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/test_quality_check.py @@ -28,6 +28,7 @@ from pathlib import Path from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "branch_level" @@ -175,28 +176,6 @@ TOTAL_ITEMS = _PATTERN_ITEMS + _MODULE_COVERAGE_ITEMS # ============================================= -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # ============================================= # FILE HELPERS # ============================================= diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/todo_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/todo_check.py index 433af707..ce9428cb 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/todo_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/todo_check.py @@ -21,6 +21,7 @@ from typing import Dict from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "all_files" @@ -35,26 +36,6 @@ _TAG_RE = re.compile( ) -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - # Must match standard - if rule.get("standard") and rule.get("standard") != standard: - continue - # Must match file (check if rule file path is in the full path) - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - # Check line-specific bypass - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - def check_module(module_path: str, bypass_rules: list | None = None) -> Dict: """ Check a Python file for TODO/FIXME/HACK/XXX comments. diff --git a/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py b/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py index 31a1ab9f..6e13b8a1 100644 --- a/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py +++ b/src/aipass/seedgo/apps/handlers/aipass_standards/unused_function_check.py @@ -31,6 +31,7 @@ from pathlib import Path from aipass.prax import logger from aipass.seedgo.apps.handlers.json import json_handler +from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed AUDIT_SCOPE = "branch_level" @@ -78,23 +79,6 @@ _MAIN_BLOCK_RE = re.compile( # -- Bypass helper ------------------------------------------------------------ -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.""" - if not bypass_rules: - return False - for rule in bypass_rules: - if rule.get("standard") and rule.get("standard") != standard: - continue - rule_file = rule.get("file", "") - if rule_file and rule_file not in file_path: - continue - rule_lines = rule.get("lines", []) - if rule_lines and line is not None and line not in rule_lines: - continue - return True - return False - - # -- File collection ---------------------------------------------------------- diff --git a/src/aipass/seedgo/apps/handlers/bypass/utils.py b/src/aipass/seedgo/apps/handlers/bypass/utils.py new file mode 100644 index 00000000..d053fd22 --- /dev/null +++ b/src/aipass/seedgo/apps/handlers/bypass/utils.py @@ -0,0 +1,52 @@ +# =================== AIPass ==================== +# Name: utils.py +# Description: Shared bypass checking utility for standards checkers +# Version: 1.0.0 +# Created: 2026-04-27 +# Modified: 2026-04-27 +# ============================================= + +"""Shared bypass checking utility for standards checkers.""" + +from aipass.seedgo.apps.handlers.json import json_handler + + +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. + + Args: + file_path: Path to the file being checked + standard: Standard name (e.g., 'cli', 'imports') + line: Optional specific line number of the violation + bypass_rules: List of bypass rules from .seedgo/bypass.json + + Returns: + True if this violation should be bypassed + """ + if not bypass_rules: + return False + for rule in bypass_rules: + if rule.get("standard") and rule.get("standard") != standard: + continue + rule_file = rule.get("file", "") + if rule_file and rule_file not in file_path: + continue + rule_lines = rule.get("lines", []) + if rule_lines and line is not None and line not in rule_lines: + continue + json_handler.log_operation( + "bypass_matched", + { + "file": file_path, + "standard": standard, + "line": line, + "rule_file": rule_file, + }, + ) + return True + return False