fix(seedgo): AST checker accuracy arc — 13 false positives gone fleet-wide, 2 legit hooks bypasses documented, checkers/audit/checklist aligned, fixture refreshed, stale bypass entries purged (devpulse/memory/seedgo). Fleet audit 100 pct. S330 night arc

This commit is contained in:
AIOSAI
2026-07-21 18:04:32 -07:00
parent 73baa0005f
commit 57285740f2
22 changed files with 747 additions and 238 deletions
+8
View File
@@ -11,6 +11,14 @@ PyPI version — not the changelog header.
## [2026-07-21]
**fix(seedgo)** — checker accuracy arc (S330): AST-based import analysis
lands in the checkers (dead_code, encapsulation, handlers, readme,
test_quality, unused_function) — 13 false positives eliminated fleet-wide,
2 real hooks imports that legitimately bypass the pattern documented instead
of suppressed. branch_audit, checklist and ignore_handler aligned; provider
hooks snapshot fixture refreshed; stale bypass entries for deleted tools
purged across branches (devpulse, memory, seedgo, hooks). Fleet audit 100%.
**feat(hooks)** — hook sound layer + temporal grounding. Sounds now mirror
the log across the hook fleet (prompt, lifecycle, notification, security
handlers) — audible liveness for the whole layer, verified live (2465 green,
-65
View File
@@ -70,71 +70,6 @@
"file": "tests/test_owner_guard.py",
"reason": "Unit test imports the owner guard handler (its SUT) and patches spawn.registry's get_owner/is_owner — the guard is a shared handler primitive with no apps/modules/ command entry point. Same direct-SUT pattern as test_compass_store.py. #681."
},
{
"standard": "encapsulation",
"file": "tools/spot_check.py",
"reason": "Standalone diagnostic tool script, not a handler."
},
{
"standard": "debug_print",
"file": "tools/spot_check.py",
"reason": "Standalone CLI tool — print() is the intended output method."
},
{
"standard": "silent_catch",
"file": "tools/hook_engine_poc/engine.py",
"reason": "POC hook engine — stdlib only, no prax logger. Uses sys.stderr for error reporting."
},
{
"standard": "error_handling",
"file": "tools/hook_engine_poc/engine.py",
"reason": "POC hook engine — exception handlers write to stderr, not prax logger."
},
{
"standard": "silent_catch",
"file": "tools/hook_engine_poc/test_engine.py",
"reason": "POC test harness — catches test exceptions to report pass/fail, writes to stderr."
},
{
"standard": "imports",
"file": "tools/hook_engine_poc/test_engine.py",
"reason": "POC test harness — sys.path needed to import engine.py from same directory."
},
{
"standard": "trigger",
"file": "tools/hook_engine_poc/test_engine.py",
"reason": "POC test harness — .unlink() clears ephemeral JSONL log between tests, not a tracked file."
},
{
"standard": "help_text",
"file": "tools/hook_engine_poc/test_engine.py",
"reason": "POC test harness — usage example in docstring."
},
{
"standard": "debug_print",
"file": "tools/rm_shim/redteam_suite.py",
"reason": "Standalone red-team diagnostic runner (FPLAN-0250 Phase 6) — print() IS the report output, same as broker_acceptance_test.py."
},
{
"standard": "encapsulation",
"file": "tools/rm_shim/redteam_suite.py",
"reason": "Red-team tool imports the real broker daemon/client + sandbox module directly to exercise them under live conditions — that is the point of an integration probe, not a handler."
},
{
"standard": "imports",
"file": "tools/rm_shim/redteam_suite.py",
"reason": "Standalone script run via 'python tools/...' — sys.path insert lets it import the production modules it red-teams without being pip-installed."
},
{
"standard": "help_text",
"file": "tools/rm_shim/redteam_suite.py",
"reason": "Diagnostic script — docstring shows the 'python tools/...' invocation; it is not a drone-routed module."
},
{
"standard": "documentation",
"file": "tools/rm_shim/redteam_suite.py",
"reason": "Result.ok/bad are 2-line internal report helpers in a diagnostic script — self-evident, docstrings redundant."
},
{
"standard": "encapsulation",
"file": "tests/test_compass_store.py",
-25
View File
@@ -759,31 +759,6 @@
"standard": "meta",
"reason": "Test file — META block present at lines 1-7; hook false-positive on test file format."
},
{
"file": "tools/migrate_entries.py",
"standard": "architecture",
"reason": "Standalone utility script in tools/ — intentionally outside 3-layer apps/ structure."
},
{
"file": "tools/strip_trinity_limits.py",
"standard": "architecture",
"reason": "One-time migration script in tools/ — intentionally outside 3-layer apps/ structure."
},
{
"file": "tools/strip_trinity_limits.py",
"standard": "log_visibility",
"reason": "Standalone script — cannot import prax logger. Uses stdlib logging."
},
{
"file": "tools/migrate_entries.py",
"standard": "silent_catch",
"reason": "Standalone script — cannot import prax logger. Errors reported via stderr print + result dict."
},
{
"file": "tools/migrate_entries.py",
"standard": "debug_print",
"reason": "Standalone script — print(stderr) is the error reporting mechanism. No prax available."
},
{
"file": "tests/test_detector.py",
"standard": "architecture",
-5
View File
@@ -195,11 +195,6 @@
"standard": "deep_nesting",
"reason": "scan() depth 6 — state machine tracking file types across standards with nested conditionals, inherent to triplet completeness detection"
},
{
"file": "tools/triplet_scanner.py",
"standard": "deep_nesting",
"reason": "scan() depth 6 — state machine tracking file types across standards with nested conditionals, inherent to triplet completeness detection"
},
{
"file": "apps/handlers/aipass_standards/introspection_check.py",
"standard": "deep_nesting",
+4 -2
View File
@@ -120,9 +120,9 @@ seedgo/
│ │ ├── branch_audit.py # Per-branch scoring engine
│ │ ├── discovery.py # Branch discovery (CWD-first registry)
│ │ └── audit_display.py # Rich result formatting
│ ├── bypass/ # Bypass system
│ ├── bypass/ # Bypass + ignore systems
│ │ ├── bypass_handler.py # .seedgo/bypass.json loader
│ │ └── ignore_handler.py # .seedgo/ignore patterns
│ │ └── ignore_handler.py # Audit ignore patterns + .seedgoignore engine
│ ├── config/ # Configuration handlers
│ ├── diagnostics/ # Pyright integration + branch discovery
│ ├── json/ # JSON tracking (json_handler)
@@ -146,6 +146,8 @@ seedgo/
**Bypass system:** `.seedgo/bypass.json` per branch. Each entry has file, standard, optional lines, and required reason. Checkers call `is_bypassed()` per violation. Bypass is intentional documented deviation, not ignoring.
**`.seedgoignore` (throwaway paths, no reason required):** Drop a `.seedgoignore` file into any directory to exclude matching files/dirs from scans, audits, and the per-file checklist — same gitignore-style patterns and per-directory nesting semantics as a real `.gitignore` (via `pathspec`). Scope is exactly that directory's subtree; a nested `.seedgoignore` adds further excludes on top of any ancestor's, it doesn't replace them. A global default (`tools/`) applies fleet-wide with zero setup, since every branch's `apps/tools/` is deliberate throwaway prototyping space — quick scripts for fast answers, not standards-compliant by design. Unlike bypass (documented exception to a specific standard on a specific file), `.seedgoignore` removes the file from consideration entirely and needs no reason. It does **not** touch diagnostics (ruff/pyright) — those keep running on ignored files so auto-fix still catches real errors while you write; only standards checks skip them. See `ignore_handler.load_ignore_entries()` / `is_seedgo_ignored()`.
---
## The 40 Standards
@@ -24,6 +24,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
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored, load_ignore_entries
from aipass.seedgo.apps.handlers.aipass_standards.skip_dirs import SOURCE_SKIP_DIRS, is_disabled_file
AUDIT_SCOPE = "branch_level"
@@ -42,12 +43,17 @@ _SKIP_DIRS = SOURCE_SKIP_DIRS | {"tests", "json_templates"}
# =============================================
def _should_skip(path: Path) -> bool:
"""Check whether any parent directory component is in the skip set or file is disabled."""
return any(part in _SKIP_DIRS for part in path.parts) or is_disabled_file(path.name)
def _should_skip(path: Path, branch_root: Path, ignore_entries: list) -> bool:
"""Check whether any parent directory component is in the skip set, file is
disabled, or the path is excluded via .seedgoignore / the global default."""
return (
any(part in _SKIP_DIRS for part in path.parts)
or is_disabled_file(path.name)
or is_seedgo_ignored(str(path), branch_root, ignore_entries)
)
def _collect_scannable_files(apps_dir: Path) -> list[Path]:
def _collect_scannable_files(apps_dir: Path, branch_root: Path, ignore_entries: list) -> list[Path]:
"""
Collect .py files from apps/modules/ and apps/handlers/ that should be
checked for usage. Skips __init__.py, __pycache__, .archive, etc.
@@ -60,20 +66,20 @@ def _collect_scannable_files(apps_dir: Path) -> list[Path]:
for py_file in subdir.rglob("*.py"):
if py_file.name == "__init__.py":
continue
if _should_skip(py_file):
if _should_skip(py_file, branch_root, ignore_entries):
continue
targets.append(py_file)
return sorted(targets)
def _collect_source_text(apps_dir: Path) -> str:
def _collect_source_text(apps_dir: Path, branch_root: Path, ignore_entries: list) -> str:
"""
Read ALL .py files under apps/ into a single string for searching.
This is the corpus we search for references.
"""
parts: list[str] = []
for py_file in apps_dir.rglob("*.py"):
if _should_skip(py_file):
if _should_skip(py_file, branch_root, ignore_entries):
continue
try:
parts.append(py_file.read_text(encoding="utf-8", errors="ignore"))
@@ -266,7 +272,8 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict:
entry_point_name = branch_name
# Collect scannable files
targets = _collect_scannable_files(apps_dir)
ignore_entries = load_ignore_entries(bp)
targets = _collect_scannable_files(apps_dir, bp, ignore_entries)
if not targets:
result = {
"passed": True,
@@ -287,7 +294,7 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict:
return result
# Build the source corpus (all .py content from apps/)
source_text = _collect_source_text(apps_dir)
source_text = _collect_source_text(apps_dir, bp, ignore_entries)
# Check each target for references
total_files = len(targets)
@@ -28,6 +28,11 @@ from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed
# Audit scope: all Python files
AUDIT_SCOPE = "all_files"
# Cross-branch/cross-package handler imports are import-statement checks, so a
# violation hiding in a package marker file must not be invisible to the audit
# the way it would be for content checkers (dead code, naming, etc.).
INCLUDE_INIT_FILES = True
def _find_registry() -> Path:
"""Find AIPASS_REGISTRY.json by walking up from this file's location."""
@@ -13,9 +13,10 @@ Validates handler compliance with AIPass handler standards.
Checks handler independence, auto-detection pattern, no orchestration.
"""
import ast
import re
from pathlib import Path
from typing import Dict, List, Optional
from typing import Dict, Iterator, List, Optional, Tuple
from aipass.prax import logger
from aipass.seedgo.apps.handlers.json import json_handler
@@ -24,6 +25,11 @@ from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed
# Audit scope: all Python files
AUDIT_SCOPE = "all_files"
# Cross-handler/orchestration imports are purely import-statement checks, so a
# violation hiding in a package marker file must not be invisible to the audit
# the way it would be for content checkers (dead code, naming, etc.).
INCLUDE_INIT_FILES = True
def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
"""
@@ -121,6 +127,39 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
return {"passed": overall_passed, "checks": checks, "score": score, "standard": "HANDLERS"}
def _iter_import_modules(content: str) -> Iterator[Tuple[int, str, List[str]]]:
"""
Parse content and yield (line_number, dotted_module, imported_names) for every
real import statement — string literals, comments and docstrings can never
produce a hit because the AST only contains actual import nodes.
Relative imports (from . import x) are omitted entirely: they can't reference
an absolute dotted path like "apps.handlers"/"apps.modules" and are always
same-package, so callers don't need to special-case them.
"""
try:
tree = ast.parse(content)
except SyntaxError as e:
logger.info("Skipping import scan: SyntaxError during parse: %s", e)
return
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
if node.level:
continue
yield node.lineno, node.module or "", [alias.name for alias in node.names]
elif isinstance(node, ast.Import):
for alias in node.names:
yield node.lineno, alias.name, []
def _line_text(lines: List[str], lineno: int, fallback: str) -> str:
"""Return the source line at 1-indexed lineno, or a fallback if out of range."""
if 0 < lineno <= len(lines):
return lines[lineno - 1].strip()
return fallback
def check_handler_independence(content: str, lines: List[str], module_path: str) -> Dict:
"""
Check handler independence - no cross-handler imports except defaults
@@ -141,58 +180,26 @@ def check_handler_independence(content: str, lines: List[str], module_path: str)
own_package = path_parts[i + 1]
break
in_docstring = False
for i, line in enumerate(lines, 1):
stripped = line.strip()
# Track docstrings (handle both single-line and multi-line)
if stripped.startswith('"""') or stripped.startswith("'''"):
quote_marker = '"""' if stripped.startswith('"""') else "'''"
# Count occurrences of the quote marker
quote_count = stripped.count(quote_marker)
if quote_count >= 2:
# Single-line docstring (opening and closing on same line)
continue # Skip this line but don't toggle state
else:
# Multi-line docstring boundary
in_docstring = not in_docstring
# Skip docstrings, comments and empty lines
if in_docstring or not stripped or stripped.startswith("#"):
for lineno, module, names in _iter_import_modules(content):
if "apps.handlers" not in module:
continue
# Check for handler imports
if "apps.handlers" in stripped and ("from " in stripped or "import " in stripped):
# Skip if in a string (rough check)
if '"from ' in stripped or "'from " in stripped:
continue
# Allowed: Default handlers (json_handler)
if module.endswith("handlers.json") and "json_handler" in names:
continue
# Extract code part (before comment)
code_part = stripped.split("#")[0] if "#" in stripped else stripped
# Allowed: Same package absolute imports
if own_package and f"handlers.{own_package}" in module:
continue
if "apps.handlers" not in code_part:
continue
# Allowed: Default handlers (json_handler)
if "handlers.json import json_handler" in code_part:
continue
# Allowed: Same package imports (relative imports like "from .decorators")
if code_part.strip().startswith("from ."):
continue
# Allowed: Same package absolute imports
if own_package and f"handlers.{own_package}" in code_part:
continue
# Forbidden: Cross-handler imports
forbidden_imports.append(f"line {i}: {stripped}")
# Forbidden: Cross-handler imports
forbidden_imports.append(f"line {lineno}: {_line_text(lines, lineno, module)}")
if forbidden_imports:
return {
"name": "Handler independence",
"passed": False,
"message": f"Cross-handler imports detected (except defaults): {forbidden_imports[0]}",
"message": "Cross-handler imports detected (except defaults): " + "; ".join(forbidden_imports),
}
return {"name": "Handler independence", "passed": True, "message": "No forbidden cross-handler imports detected"}
@@ -239,50 +246,22 @@ def check_no_orchestration(content: str, lines: List[str]) -> Optional[Dict]:
"""
module_imports = []
in_docstring = False
for i, line in enumerate(lines, 1):
stripped = line.strip()
# Track docstrings (handle both single-line and multi-line)
if stripped.startswith('"""') or stripped.startswith("'''"):
quote_marker = '"""' if stripped.startswith('"""') else "'''"
# Count occurrences of the quote marker
quote_count = stripped.count(quote_marker)
if quote_count >= 2:
# Single-line docstring (opening and closing on same line)
continue # Skip this line but don't toggle state
else:
# Multi-line docstring boundary
in_docstring = not in_docstring
# Skip docstrings, comments and empty lines
if in_docstring or not stripped or stripped.startswith("#"):
for lineno, module, _names in _iter_import_modules(content):
if "apps.modules" not in module:
continue
# Check for module imports
if "apps.modules" in stripped and ("from " in stripped or "import " in stripped):
# Skip if in a string
if '"from ' in stripped or "'from " in stripped:
continue
# Allowed: Service imports (prax.apps.modules.logger, cli.apps.modules)
if "prax.apps.modules.logger" in module or "cli.apps.modules" in module:
continue
# Extract code part
code_part = stripped.split("#")[0] if "#" in stripped else stripped
if "apps.modules" not in code_part:
continue
# Allowed: Service imports (prax.apps.modules.logger, cli.apps.modules)
if "prax.apps.modules.logger" in code_part or "cli.apps.modules" in code_part:
continue
# Forbidden: Module imports (orchestration)
module_imports.append(f"line {i}: {stripped}")
# Forbidden: Module imports (orchestration)
module_imports.append(f"line {lineno}: {_line_text(lines, lineno, module)}")
if module_imports:
return {
"name": "No orchestration",
"passed": False,
"message": f"Handler imports modules (orchestration): {module_imports[0]}",
"message": "Handler imports modules (orchestration): " + "; ".join(module_imports),
}
return {"name": "No orchestration", "passed": True, "message": "No module imports detected (pure implementation)"}
@@ -31,6 +31,7 @@ from aipass.prax import logger
from aipass.seedgo.apps.handlers.json import json_handler
from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed
from aipass.seedgo.apps.handlers.aipass_standards.skip_dirs import SOURCE_SKIP_DIRS, is_disabled_file
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored, load_ignore_entries
# Audit scope: entry points only (apps/{name}.py)
AUDIT_SCOPE = "entry_point"
@@ -503,12 +504,16 @@ def _extract_test_counts(content: str) -> List[int]:
def _count_test_functions(tests_dir: Path) -> int:
"""Count `def test_` functions in all test_*.py files under tests/."""
count = 0
branch_root = tests_dir.parent
ignore_entries = load_ignore_entries(branch_root)
test_func_pattern = re.compile(r"^\s*def\s+test_", re.MULTILINE)
for test_file in tests_dir.rglob("test_*.py"):
if any(part in SOURCE_SKIP_DIRS for part in test_file.relative_to(tests_dir).parts):
continue
if is_disabled_file(test_file.name):
continue
if is_seedgo_ignored(str(test_file), branch_root, ignore_entries):
continue
try:
source = test_file.read_text(encoding="utf-8")
count += len(test_func_pattern.findall(source))
@@ -30,6 +30,7 @@ from aipass.prax import logger
from aipass.seedgo.apps.handlers.json import json_handler
from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed
from aipass.seedgo.apps.handlers.aipass_standards.skip_dirs import SOURCE_SKIP_DIRS, is_disabled_file
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored, load_ignore_entries
AUDIT_SCOPE = "branch_level"
@@ -180,7 +181,7 @@ def _should_skip_file(name: str) -> bool:
return is_disabled_file(name)
def _find_test_files_broad(branch_path: Path) -> list[Path]:
def _find_test_files_broad(branch_path: Path, ignore_entries: list) -> list[Path]:
"""Find all test files for module coverage analysis.
Broader than _find_all_test_files — also finds scattered test files
@@ -197,6 +198,8 @@ def _find_test_files_broad(branch_path: Path) -> list[Path]:
continue
if any(_should_skip_dir(part) for part in py_file.relative_to(tests_dir).parts):
continue
if is_seedgo_ignored(str(py_file), branch_path, ignore_entries):
continue
resolved = py_file.resolve()
if resolved not in seen:
seen.add(resolved)
@@ -208,6 +211,8 @@ def _find_test_files_broad(branch_path: Path) -> list[Path]:
continue
if py_file.name in ("__init__.py", "conftest.py") or _should_skip_file(py_file.name):
continue
if is_seedgo_ignored(str(py_file), branch_path, ignore_entries):
continue
if py_file.name.startswith("test_") or py_file.name.endswith("_test.py"):
resolved = py_file.resolve()
if resolved not in seen:
@@ -234,7 +239,7 @@ def _analyze_test_file_imports(source: str) -> set[str]:
return tested_modules
def _collect_testable_modules(branch_path: Path) -> set[str]:
def _collect_testable_modules(branch_path: Path, ignore_entries: list) -> set[str]:
"""Collect module names from apps/modules/ and apps/handlers/.
Returns set of module names:
@@ -255,17 +260,21 @@ def _collect_testable_modules(branch_path: Path) -> set[str]:
and item.suffix == ".py"
and item.name != "__init__.py"
and not _should_skip_file(item.name)
and not is_seedgo_ignored(str(item), branch_path, ignore_entries)
):
modules.add(item.stem)
handlers_dir = apps_dir / "handlers"
if handlers_dir.is_dir():
for item in sorted(handlers_dir.iterdir()):
if _should_skip_dir(item.name):
if _should_skip_dir(item.name) or is_seedgo_ignored(str(item), branch_path, ignore_entries):
continue
if item.is_dir() and item.name != "__pycache__":
has_py = any(
f.suffix == ".py" and f.name != "__init__.py" and not _should_skip_file(f.name)
f.suffix == ".py"
and f.name != "__init__.py"
and not _should_skip_file(f.name)
and not is_seedgo_ignored(str(f), branch_path, ignore_entries)
for f in item.iterdir()
if f.is_file()
)
@@ -282,7 +291,7 @@ def _collect_testable_modules(branch_path: Path) -> set[str]:
return modules
def _find_all_test_files(branch_path: Path) -> list[Path]:
def _find_all_test_files(branch_path: Path, ignore_entries: list) -> list[Path]:
"""Find all test files and conftest.py in the branch's tests/ directory.
Scans for any test_*.py file plus conftest.py -- no naming requirements.
@@ -295,6 +304,8 @@ def _find_all_test_files(branch_path: Path) -> list[Path]:
for p in sorted(tests_dir.iterdir()):
if not p.is_file() or p.suffix != ".py":
continue
if is_seedgo_ignored(str(p), branch_path, ignore_entries):
continue
if p.name.startswith("test_") or p.name == "conftest.py":
results.append(p)
@@ -395,7 +406,8 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict:
}
# Phase 1: Find all test files
test_files = _find_all_test_files(bp)
ignore_entries = load_ignore_entries(bp)
test_files = _find_all_test_files(bp, ignore_entries)
if not test_files:
checks.append(
@@ -470,7 +482,7 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict:
# Phase 4: Module coverage (category 11 — from test_coverage_check.py)
# Uses broader file discovery + import-based module mapping
broad_test_files = _find_test_files_broad(bp)
broad_test_files = _find_test_files_broad(bp, ignore_entries)
total_tests = 0
tested_modules: set[str] = set()
for tf in broad_test_files:
@@ -483,7 +495,7 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict:
if total_tests == 0:
tested_modules = set()
all_modules = _collect_testable_modules(bp)
all_modules = _collect_testable_modules(bp, ignore_entries)
total_modules = len(all_modules)
# 3 module coverage items
@@ -32,6 +32,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
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored, load_ignore_entries
from aipass.seedgo.apps.handlers.aipass_standards.skip_dirs import SOURCE_SKIP_DIRS, is_disabled_file
AUDIT_SCOPE = "branch_level"
@@ -60,18 +61,23 @@ _MAIN_BLOCK_RE = re.compile(
# -- File collection ----------------------------------------------------------
def _should_skip(path: Path) -> bool:
"""Return True if any path component is in the skip set or file is disabled."""
return any(part in SKIP_DIRS for part in path.parts) or is_disabled_file(path.name)
def _should_skip(path: Path, branch_root: Path, ignore_entries: list) -> bool:
"""Return True if any path component is in the skip set, file is disabled,
or the path is excluded via .seedgoignore / the global default."""
return (
any(part in SKIP_DIRS for part in path.parts)
or is_disabled_file(path.name)
or is_seedgo_ignored(str(path), branch_root, ignore_entries)
)
def _collect_python_files(branch_path: Path) -> list[Path]:
def _collect_python_files(branch_path: Path, ignore_entries: list) -> list[Path]:
"""Collect all .py files in the branch, skipping irrelevant dirs."""
files: list[Path] = []
if not branch_path.is_dir():
return files
for py_file in branch_path.rglob("*.py"):
if _should_skip(py_file):
if _should_skip(py_file, branch_path, ignore_entries):
continue
files.append(py_file)
return sorted(files)
@@ -216,7 +222,8 @@ def check_branch(branch_path: str, bypass_rules: list | None = None) -> dict:
}
# Phase 1: Collect all .py files
py_files = _collect_python_files(branch)
ignore_entries = load_ignore_entries(branch)
py_files = _collect_python_files(branch, ignore_entries)
if not py_files:
json_handler.log_operation(
"check_completed",
@@ -42,19 +42,27 @@ def discover_checkers(pack_path: Path | None = None) -> Dict[str, Any]:
return checkers
def _collect_py_files(branch_path: Path) -> List[Dict[str, str]]:
"""Collect auditable .py files from apps/, respecting ignore patterns."""
def _collect_py_files(branch_path: Path, include_init: bool = False) -> List[Dict[str, str]]:
"""Collect auditable .py files from apps/, respecting ignore patterns.
__init__.py package markers are excluded by default — most checkers are
content-focused (dead code, naming, nesting) and __init__.py is typically
boilerplate. Pass include_init=True for import-statement checkers, where a
real cross-handler import hiding in a package marker must not go unseen.
"""
apps_dir = branch_path / "apps"
if not apps_dir.exists():
return []
ign = ignore_handler.get_audit_ignore_patterns()
ignore_entries = ignore_handler.load_ignore_entries(branch_path)
return [
{"file": str(f), "name": f.name}
for f in apps_dir.rglob("*.py")
if f.name != "__init__.py"
if (include_init or f.name != "__init__.py")
and not is_disabled_file(f.name)
and not is_throwaway_path(str(f))
and not any(p in str(f).lower() for p in ign)
and not ignore_handler.is_seedgo_ignored(str(f), branch_path, ignore_entries)
]
@@ -132,6 +140,7 @@ def audit_branch(branch: Dict[str, str], bypass_rules: list, pack_path: Path | N
"""Audit a branch for standards compliance. Returns backward-compatible dict."""
entry_file, branch_path = branch["entry_file"], Path(branch["path"])
checkers, all_files = discover_checkers(pack_path), _collect_py_files(branch_path)
files_with_init: List[Dict[str, str]] | None = None
# Discover diagnostics checker from handlers/diagnostics/ (outside pack dirs)
diag_mod = _load_diagnostics_checker()
@@ -161,7 +170,12 @@ def audit_branch(branch: Dict[str, str], bypass_rules: list, pack_path: Path | N
results[name], scores[name] = {"passed": False, "score": 0, "error": str(e)}, 0
# All-files scope: scan every .py file, override score with average
if scope == "all_files" and all_files:
v, s = _run_all_files(checker, name, all_files, bypass_rules)
scan_files = all_files
if getattr(checker, "INCLUDE_INIT_FILES", False):
if files_with_init is None:
files_with_init = _collect_py_files(branch_path, include_init=True)
scan_files = files_with_init
v, s = _run_all_files(checker, name, scan_files, bypass_rules)
all_violations[name] = v
if s:
avg_score = int(sum(s) / len(s))
@@ -11,14 +11,23 @@ Ignore Pattern Configuration Handler
Provides ignore patterns for audit file filtering, template baseline checking,
and deprecated pattern tracking. Pure configuration with helper functions.
Also provides the .seedgoignore engine — a gitignore-style dotfile droppable
into any directory (per-directory scope, same nesting semantics as .gitignore)
plus a global default so agents' tools/ dirs are ignored fleet-wide with zero
per-branch setup. See DEFAULT_IGNORE_PATTERNS / is_seedgo_ignored().
"""
# =============================================
# IMPORTS
# =============================================
from typing import List
from pathlib import Path
from typing import List, Optional, Tuple
import pathspec
from aipass.prax import logger
from aipass.seedgo.apps.handlers.json import json_handler
# =============================================
@@ -111,6 +120,95 @@ def get_deprecated_patterns() -> dict:
return DEPRECATED_PATTERNS.copy()
# =============================================
# SEEDGO_IGNORE — gitignore-style, per-directory
# =============================================
# Dotfile name — droppable into any directory in a branch, gitignore-style
# patterns (via pathspec), scoped to that directory's subtree exactly like a
# real .gitignore.
IGNORE_FILENAME = ".seedgoignore"
# Global default — applied to every branch with zero per-branch setup.
# Agents' tools/ dirs are deliberate throwaway prototyping space (quick
# scripts for fast answers) — not standards-compliant by design, and that's
# fine and wanted (Patrick ruling).
DEFAULT_IGNORE_PATTERNS: List[str] = [
"tools/",
]
def _iter_seedgo_ignore_files(branch_root: Path) -> List[Path]:
"""Return every .seedgoignore file under branch_root, shallowest first."""
return sorted(branch_root.rglob(IGNORE_FILENAME), key=lambda p: len(p.parts))
def load_ignore_entries(branch_root: Path) -> List[Tuple[str, "pathspec.PathSpec"]]:
"""Build the ordered (scope, PathSpec) list for a branch.
scope "" is the global default and applies branch-wide. Each discovered
.seedgoignore file adds a scope equal to its own directory (relative to
branch_root) — its patterns are relative to that directory and only
match within its subtree, same nesting semantics as a real .gitignore.
Args:
branch_root: Absolute path to the branch root.
Returns:
Ordered list of (scope, PathSpec) pairs, global default first.
"""
root = Path(branch_root).resolve()
entries: List[Tuple[str, "pathspec.PathSpec"]] = [
("", pathspec.PathSpec.from_lines("gitignore", DEFAULT_IGNORE_PATTERNS))
]
for ignore_file in _iter_seedgo_ignore_files(root):
scope = ignore_file.parent.relative_to(root).as_posix()
if scope == ".":
scope = ""
try:
lines = ignore_file.read_text(encoding="utf-8").splitlines()
except OSError as exc:
logger.info("[ignore_handler] Cannot read %s: %s", ignore_file, exc)
continue
entries.append((scope, pathspec.PathSpec.from_lines("gitignore", lines)))
json_handler.log_operation("seedgo_ignore_loaded", {"branch_root": str(root), "files": len(entries) - 1})
return entries
def is_seedgo_ignored(
file_path: str, branch_root: Path, entries: Optional[List[Tuple[str, "pathspec.PathSpec"]]] = None
) -> bool:
"""Check whether file_path is ignored via .seedgoignore or the global default.
Args:
file_path: Absolute path to the candidate file.
branch_root: Absolute path to the branch root.
entries: Pre-loaded result of load_ignore_entries(); loaded fresh if omitted.
Returns:
True when scans/audits/checklists/checker-style enforcement should skip this path.
"""
root = Path(branch_root).resolve()
try:
rel = Path(file_path).resolve().relative_to(root).as_posix()
except ValueError as exc:
logger.info("[ignore_handler] %s not under branch root %s: %s", file_path, root, exc)
return False
if entries is None:
entries = load_ignore_entries(root)
for scope, spec in entries:
if scope and rel != scope and not rel.startswith(scope + "/"):
continue
sub_rel = rel[len(scope) + 1 :] if scope else rel
if spec.match_file(sub_rel):
return True
return False
# =============================================
# MODULE INITIALIZATION
# =============================================
+19 -4
View File
@@ -45,6 +45,9 @@ from aipass.seedgo.apps.handlers.bypass.bypass_handler import (
# Throwaway / prototype detection
from aipass.seedgo.apps.handlers.aipass_standards.skip_dirs import is_prototype_file, is_throwaway_path
# .seedgoignore — gitignore-style per-directory + global default (tools/)
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored
# JSON handler for tracking
from aipass.seedgo.apps.handlers.json import json_handler
@@ -140,6 +143,10 @@ def run_checklist(file_path: str, pack_name: str = "aipass", prototype: bool = F
if prototype or is_prototype_file(resolved):
return [{"standard": "(skip)", "passed": True, "detail": "Prototype mode — standards skipped"}]
branch_path = _resolve_branch_path(resolved)
if branch_path is not None and is_seedgo_ignored(resolved, branch_path):
return [{"standard": "(skip)", "passed": True, "detail": "Ignored via .seedgoignore"}]
# Discover pack path
pack_path = _resolve_pack_path(pack_name)
if pack_path is None:
@@ -191,20 +198,28 @@ def _resolve_pack_path(pack_name: str) -> Optional[Path]:
return None
def _load_bypass_for_file(file_path: str) -> list:
"""Load bypass rules for the branch containing file_path."""
def _resolve_branch_path(file_path: str) -> Optional[Path]:
"""Resolve the absolute branch root containing file_path, or None."""
branch = get_branch_from_path(file_path)
if branch is None:
return []
return None
raw_path = branch.get("path", "")
if not raw_path:
return []
return None
bp = Path(raw_path)
if not bp.is_absolute():
from aipass.seedgo.apps.handlers.bypass.bypass_handler import _find_registry
registry_path = _find_registry()
bp = (registry_path.parent / bp).resolve()
return bp
def _load_bypass_for_file(file_path: str) -> list:
"""Load bypass rules for the branch containing file_path."""
bp = _resolve_branch_path(file_path)
if bp is None:
return []
return load_bypass_rules(str(bp))
+99 -29
View File
@@ -1,5 +1,21 @@
{
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py UserPromptSubmit:presence_gate"
}
]
},
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py UserPromptSubmit:persistent_alert"
}
]
},
{
"hooks": [
{
@@ -40,6 +56,22 @@
}
]
},
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py UserPromptSubmit:compass_recall"
}
]
},
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py UserPromptSubmit:feedback_pulse"
}
]
},
{
"hooks": [
{
@@ -48,15 +80,53 @@
"timeout": 120
}
]
}
],
"PreToolUse": [
},
{
"matcher": "Bash|Edit|MultiEdit|Write|Read|Grep|Glob|WebSearch|WebFetch|Task",
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py PreToolUse"
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py UserPromptSubmit:context_gauge",
"timeout": 30
}
]
},
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py UserPromptSubmit:temporal",
"timeout": 30
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py Notification"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py Stop"
}
]
}
],
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py SessionStart:cadence_reset",
"timeout": 30
}
]
}
@@ -72,6 +142,17 @@
]
}
],
"PreToolUse": [
{
"matcher": "Bash|Edit|MultiEdit|Write|Read|Grep|Glob|WebSearch|WebFetch|Task",
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py PreToolUse"
}
]
}
],
"SubagentStop": [
{
"hooks": [
@@ -82,26 +163,6 @@
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py Stop"
}
]
}
],
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py Notification"
}
]
}
],
"PreCompact": [
{
"matcher": "manual",
@@ -162,14 +223,23 @@
"timeout": 120
}
]
}
],
"SessionStart": [
},
{
"matcher": "manual",
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py SessionStart:cadence_reset",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py PreCompact:pre_compact_prep",
"timeout": 30
}
]
},
{
"matcher": "auto",
"hooks": [
{
"type": "command",
"command": "$AIPASS_HOME/.venv/bin/python3 $AIPASS_HOME/src/aipass/hooks/apps/handlers/bridges/claude.py PreCompact:pre_compact_prep",
"timeout": 30
}
]
+105
View File
@@ -221,6 +221,111 @@ def test_get_deprecated_patterns_returns_dict():
assert isinstance(value, str)
# ---------------------------------------------------------------------------
# Tests -- .seedgoignore engine (load_ignore_entries / is_seedgo_ignored)
# ---------------------------------------------------------------------------
def test_global_default_ignores_tools_dir_with_no_dotfile(tmp_path):
"""Global default (tools/) applies branch-wide even with zero .seedgoignore files."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored
tools_file = tmp_path / "apps" / "tools" / "scratch.py"
tools_file.parent.mkdir(parents=True)
tools_file.write_text("pass", encoding="utf-8")
normal_file = tmp_path / "apps" / "modules" / "real.py"
normal_file.parent.mkdir(parents=True)
normal_file.write_text("pass", encoding="utf-8")
assert is_seedgo_ignored(str(tools_file), tmp_path) is True
assert is_seedgo_ignored(str(normal_file), tmp_path) is False
def test_seedgo_ignore_dotfile_scoped_to_its_own_directory(tmp_path):
"""A .seedgoignore dropped in a subdir only affects that subdir's subtree, not siblings."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored
scratch_dir = tmp_path / "apps" / "handlers" / "experiment"
scratch_dir.mkdir(parents=True)
(scratch_dir / ".seedgoignore").write_text("*.draft.py\n", encoding="utf-8")
(scratch_dir / "wip.draft.py").write_text("pass", encoding="utf-8")
sibling_dir = tmp_path / "apps" / "handlers" / "other"
sibling_dir.mkdir(parents=True)
(sibling_dir / "wip.draft.py").write_text("pass", encoding="utf-8")
assert is_seedgo_ignored(str(scratch_dir / "wip.draft.py"), tmp_path) is True
assert is_seedgo_ignored(str(sibling_dir / "wip.draft.py"), tmp_path) is False
def test_seedgo_ignore_supports_gitignore_style_patterns(tmp_path):
"""Comments, blank lines, and negation follow standard gitignore semantics."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored
(tmp_path / ".seedgoignore").write_text(
"\n".join(["# comment", "", "scratch/", "!scratch/keep_me.py"]),
encoding="utf-8",
)
scratch_dir = tmp_path / "scratch"
scratch_dir.mkdir()
(scratch_dir / "throwaway.py").write_text("pass", encoding="utf-8")
(scratch_dir / "keep_me.py").write_text("pass", encoding="utf-8")
assert is_seedgo_ignored(str(scratch_dir / "throwaway.py"), tmp_path) is True
assert is_seedgo_ignored(str(scratch_dir / "keep_me.py"), tmp_path) is False
def test_seedgo_ignore_nested_scopes_both_apply(tmp_path):
"""A nested .seedgoignore adds to (not replaces) any ancestor .seedgoignore scopes."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored
(tmp_path / ".seedgoignore").write_text("*.rootskip\n", encoding="utf-8")
child_dir = tmp_path / "apps" / "child"
child_dir.mkdir(parents=True)
(child_dir / ".seedgoignore").write_text("*.childskip\n", encoding="utf-8")
(child_dir / "a.rootskip").write_text("pass", encoding="utf-8")
(child_dir / "b.childskip").write_text("pass", encoding="utf-8")
(child_dir / "c.py").write_text("pass", encoding="utf-8")
assert is_seedgo_ignored(str(child_dir / "a.rootskip"), tmp_path) is True
assert is_seedgo_ignored(str(child_dir / "b.childskip"), tmp_path) is True
assert is_seedgo_ignored(str(child_dir / "c.py"), tmp_path) is False
def test_is_seedgo_ignored_path_outside_branch_root_returns_false(tmp_path):
"""A file outside branch_root cannot be resolved to a relative path — treated as not ignored."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored
branch_root = tmp_path / "branch"
branch_root.mkdir()
outside_file = tmp_path / "elsewhere" / "file.py"
outside_file.parent.mkdir()
outside_file.write_text("pass", encoding="utf-8")
assert is_seedgo_ignored(str(outside_file), branch_root) is False
def test_load_ignore_entries_default_only_when_no_dotfiles(tmp_path):
"""With no .seedgoignore files present, only the global default scope is returned."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import load_ignore_entries
entries = load_ignore_entries(tmp_path)
assert len(entries) == 1
assert entries[0][0] == ""
def test_is_seedgo_ignored_accepts_precomputed_entries(tmp_path):
"""Passing pre-loaded entries skips the internal reload — same result as omitting it."""
from aipass.seedgo.apps.handlers.bypass.ignore_handler import is_seedgo_ignored, load_ignore_entries
tools_file = tmp_path / "tools" / "scratch.py"
tools_file.parent.mkdir(parents=True)
tools_file.write_text("pass", encoding="utf-8")
entries = load_ignore_entries(tmp_path)
assert is_seedgo_ignored(str(tools_file), tmp_path, entries) is True
# ---------------------------------------------------------------------------
# Tests -- utils.is_bypassed name-scoped bypass
# ---------------------------------------------------------------------------
@@ -158,6 +158,42 @@ from aipass.seedgo.apps.modules.scanner import scan_all
result = check_handlers(fp, bypass_rules=bypass)
assert result["score"] == 100
def test_handlers_reports_all_forbidden_imports(self, tmp_path: Path) -> None:
"""A file with two cross-handler imports must report both, not just the first."""
handler_dir = tmp_path / "apps" / "handlers" / "mypack"
handler_dir.mkdir(parents=True)
code = """\
from aipass.seedgo.apps.handlers.error import error_handler
from aipass.seedgo.apps.handlers.file import file_handler
def do_stuff():
return True
"""
fp = str(handler_dir / "double_violation.py")
Path(fp).write_text(code, encoding="utf-8")
result = check_handlers(fp)
independence = next(c for c in result["checks"] if c["name"] == "Handler independence")
assert "line 1" in independence["message"]
assert "line 2" in independence["message"]
def test_handlers_reports_all_orchestration_imports(self, tmp_path: Path) -> None:
"""A file with two module (orchestration) imports must report both, not just the first."""
handler_dir = tmp_path / "apps" / "handlers" / "mypack"
handler_dir.mkdir(parents=True)
code = """\
from aipass.seedgo.apps.modules.scanner import scan_all
from aipass.seedgo.apps.modules.reporter import report_all
def do_stuff():
return scan_all()
"""
fp = str(handler_dir / "double_orchestration.py")
Path(fp).write_text(code, encoding="utf-8")
result = check_handlers(fp)
orchestration = next(c for c in result["checks"] if c["name"] == "No orchestration")
assert "line 1" in orchestration["message"]
assert "line 2" in orchestration["message"]
# ===================================================================
# 3. hardcoded_key_check
@@ -238,6 +238,21 @@ class TestDeadCodeCheck:
assert result["score"] == 100
assert result["passed"] is True
def test_dead_code_ignores_tools_dir_by_default(self, mock_json, tmp_path: Path) -> None:
"""Orphan modules under apps/tools/ are skipped via the global .seedgoignore default."""
branch = _make_branch(tmp_path)
_write_file(
branch / "apps" / "tools" / "scratch.py",
"def lonely_function():\n return None\n",
)
_write_file(
branch / "apps" / (branch.name + ".py"),
"def handle_command(): pass\n",
)
result = dead_code_check_branch(str(branch))
assert result["score"] == 100
assert result["passed"] is True
# =============================================
# 5. test_quality_check (check_branch)
@@ -466,3 +481,16 @@ class TestUnusedFunctionCheck:
result = unused_function_check_branch(str(branch), bypass_rules=bypass)
assert result["score"] == 100
assert result["passed"] is True
def test_unused_function_respects_seedgo_ignore_dotfile(self, mock_json, tmp_path: Path) -> None:
"""A .seedgoignore dropped alongside an unused function's file suppresses the flag."""
branch = _make_branch(tmp_path)
experiment_dir = branch / "apps" / "handlers" / "experiment"
_write_file(experiment_dir / ".seedgoignore", "*\n")
_write_file(
experiment_dir / "scratch.py",
"def never_called():\n pass\n",
)
result = unused_function_check_branch(str(branch))
assert result["score"] == 100
assert result["passed"] is True
@@ -132,6 +132,45 @@ def test_handler_independence_same_package(tmp_path):
assert result["passed"] is True
def test_handler_independence_fstring_guard_text_not_flagged(tmp_path):
"""An f-string that mentions apps.handlers as help text is not a real import."""
content = (
"def guard():\n"
" raise ImportError(\n"
' f" from aipass.seedgo.apps.handlers.error import error_handler\\n"\n'
" )\n"
)
handler_path = str(tmp_path / "apps" / "handlers" / "audit" / "guard.py")
from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import (
check_handler_independence,
)
result = check_handler_independence(content, _lines(content), handler_path)
assert result["passed"] is True
def test_handler_independence_flags_real_import_alongside_guard_text(tmp_path):
"""A real cross-handler import is still caught even when guard text sits nearby."""
content = (
"from aipass.seedgo.apps.handlers.error import error_handler\n"
"\n"
"def guard():\n"
" raise ImportError(\n"
' f" from aipass.seedgo.apps.handlers.error import error_handler\\n"\n'
" )\n"
)
handler_path = str(tmp_path / "apps" / "handlers" / "audit" / "mixed.py")
from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import (
check_handler_independence,
)
result = check_handler_independence(content, _lines(content), handler_path)
assert result["passed"] is False
assert "line 1" in result["message"]
# ===========================================================================
# 2. handlers_check -- check_auto_detection
# ===========================================================================
@@ -238,6 +277,51 @@ def test_no_orchestration_in_docstring():
assert result["passed"] is True
def test_no_orchestration_fstring_guard_text_not_flagged():
"""An import-guard error message built from an f-string is not a real import.
Regression test: apps/handlers/__init__.py raises ImportError with help text
like `f" from {MY_BRANCH}.apps.modules.<module> import <function>\\n"` —
the old text-matching check flagged this guard text as orchestration.
"""
content = (
"MY_BRANCH = 'aipass.seedgo'\n"
"def guard():\n"
" raise ImportError(\n"
' f" from {MY_BRANCH}.apps.modules.<module> import <function>\\n"\n'
" )\n"
)
from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import (
check_no_orchestration,
)
result = check_no_orchestration(content, _lines(content))
assert result is not None
assert result["passed"] is True
def test_no_orchestration_flags_real_import_alongside_guard_text():
"""A real module import is still caught even when guard text sits nearby."""
content = (
"from aipass.seedgo.apps.modules.scanner import scan_all\n"
"\n"
"def guard():\n"
" raise ImportError(\n"
' f" from aipass.seedgo.apps.modules.<module> import <function>\\n"\n'
" )\n"
)
from aipass.seedgo.apps.handlers.aipass_standards.handlers_check import (
check_no_orchestration,
)
result = check_no_orchestration(content, _lines(content))
assert result is not None
assert result["passed"] is False
assert "line 1" in result["message"]
# ===========================================================================
# 4. log_handler_check -- check_no_raw_file_handler
# ===========================================================================
+36
View File
@@ -22,6 +22,11 @@ def _mock_infrastructure(monkeypatch):
"""Mock heavy infrastructure imports for checklist."""
import sys
from aipass.seedgo.apps.handlers.bypass.ignore_handler import (
is_seedgo_ignored as real_is_seedgo_ignored,
load_ignore_entries as real_load_ignore_entries,
)
mock_logger = MagicMock()
mock_console = MagicMock()
mock_error = MagicMock()
@@ -67,6 +72,12 @@ def _mock_infrastructure(monkeypatch):
bypass_mod.load_bypass_rules = MagicMock(return_value=[])
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass.bypass_handler", bypass_mod)
ignore_mod = MagicMock()
ignore_mod.is_seedgo_ignored = real_is_seedgo_ignored
ignore_mod.load_ignore_entries = real_load_ignore_entries
bypass_pkg.ignore_handler = ignore_mod
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass.ignore_handler", ignore_mod)
# Force re-import
monkeypatch.delitem(sys.modules, "aipass.seedgo.apps.modules.checklist", raising=False)
@@ -222,6 +233,31 @@ def test_run_checklist_prototype_marker_skips(tmp_path, monkeypatch):
assert "prototype" in results[0]["detail"].lower()
def test_run_checklist_seedgo_ignore_skips(tmp_path, monkeypatch):
"""A file under apps/tools/ is skipped via the global .seedgoignore default."""
import sys
from aipass.seedgo.apps.handlers.aipass_standards import skip_dirs
monkeypatch.setattr(skip_dirs, "_get_temp_roots", lambda: [])
branch_mod = MagicMock()
branch_mod.get_branch_from_path = MagicMock(return_value={"path": str(tmp_path)})
branch_mod.load_bypass_rules = MagicMock(return_value=[])
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass.bypass_handler", branch_mod)
from aipass.seedgo.apps.modules.checklist import run_checklist
tools_dir = tmp_path / "apps" / "tools"
tools_dir.mkdir(parents=True)
f = tools_dir / "scratch.py"
f.write_text("x = 1\n", encoding="utf-8")
results = run_checklist(str(f))
assert len(results) == 1
assert results[0]["passed"] is True
assert "seedgoignore" in results[0]["detail"].lower()
def test_run_checklist_normal_file_still_audited(tmp_path, monkeypatch):
"""A normal file without markers/temp path is still fully audited."""
import sys
@@ -32,8 +32,15 @@ def _mock_infrastructure(monkeypatch):
mock_console = MagicMock()
mock_json_handler = MagicMock()
mock_json_handler.log_operation = MagicMock(return_value=True)
from aipass.seedgo.apps.handlers.bypass.ignore_handler import (
is_seedgo_ignored as real_is_seedgo_ignored,
load_ignore_entries as real_load_ignore_entries,
)
mock_ignore_handler = MagicMock()
mock_ignore_handler.get_audit_ignore_patterns = MagicMock(return_value=[])
mock_ignore_handler.is_seedgo_ignored = real_is_seedgo_ignored
mock_ignore_handler.load_ignore_entries = real_load_ignore_entries
mock_scan_branch = MagicMock(return_value=None)
# -- prax ---------------------------------------------------------------
@@ -1087,6 +1094,27 @@ class TestCollectPyFiles:
assert "handler.py" in names
assert "__init__.py" not in names
def test_collects_init_files_when_requested(self, tmp_path, monkeypatch):
"""include_init=True keeps __init__.py — for import checkers that must not miss them."""
import sys
skip_dirs = sys.modules.get("aipass.seedgo.apps.handlers.aipass_standards.skip_dirs")
if skip_dirs:
monkeypatch.setattr(skip_dirs, "_get_temp_roots", lambda: [])
from aipass.seedgo.apps.handlers.audit.branch_audit import (
_collect_py_files,
)
apps_dir = tmp_path / "apps"
apps_dir.mkdir()
(apps_dir / "__init__.py").write_text("", encoding="utf-8")
(apps_dir / "module.py").write_text("pass", encoding="utf-8")
result = _collect_py_files(tmp_path, include_init=True)
names = [f["name"] for f in result]
assert "module.py" in names
assert "__init__.py" in names
def test_respects_ignore_patterns(self, tmp_path, monkeypatch):
"""Files matching ignore patterns are excluded."""
import sys
@@ -1134,6 +1162,29 @@ class TestCollectPyFiles:
assert "module.py" in names
assert "dashboard_sync(disabled).py" not in names
def test_respects_seedgo_ignore_tools_dir(self, tmp_path, monkeypatch):
"""Files under apps/tools/ are excluded via the global .seedgoignore default."""
import sys
skip_dirs = sys.modules.get("aipass.seedgo.apps.handlers.aipass_standards.skip_dirs")
if skip_dirs:
monkeypatch.setattr(skip_dirs, "_get_temp_roots", lambda: [])
from aipass.seedgo.apps.handlers.audit.branch_audit import (
_collect_py_files,
)
apps_dir = tmp_path / "apps"
apps_dir.mkdir()
(apps_dir / "module.py").write_text("pass", encoding="utf-8")
tools_dir = apps_dir / "tools"
tools_dir.mkdir()
(tools_dir / "scratch.py").write_text("pass", encoding="utf-8")
result = _collect_py_files(tmp_path)
names = [f["name"] for f in result]
assert "module.py" in names
assert "scratch.py" not in names
class TestExtractBranchLevelViolations:
"""Tests for _extract_branch_level_violations."""
@@ -1462,6 +1513,7 @@ def _make_checker(
checker = MagicMock()
checker.AUDIT_SCOPE = scope
checker.FILE_FILTER = None
checker.INCLUDE_INIT_FILES = False
if has_check_module:
default_mod = {
@@ -1676,6 +1728,41 @@ class TestAuditBranch:
result = branch_audit.audit_branch(branch, [])
assert "naming_violations" in result
def test_all_files_scope_include_init_files_opt_in(self, tmp_path, monkeypatch):
"""Checker with INCLUDE_INIT_FILES=True sees __init__.py; a normal checker does not."""
from aipass.seedgo.apps.handlers.audit import branch_audit
branch, branch_path = _setup_branch(tmp_path)
apps_dir = Path(branch_path) / "apps"
(apps_dir / "__init__.py").write_text("pass", encoding="utf-8")
import_checker = _make_checker(scope="all_files")
import_checker.INCLUDE_INIT_FILES = True
normal_checker = _make_checker(scope="all_files")
# tmp_path lives under the system temp dir, which is_throwaway_path
# filters out wholesale — neutralize that so _collect_py_files sees
# the fixture's files, same as it would for a real branch on disk.
monkeypatch.setattr(branch_audit, "is_throwaway_path", lambda path_str: False)
monkeypatch.setattr(
branch_audit,
"discover_checkers",
lambda pack_path=None: {"handlers": import_checker, "naming": normal_checker},
)
monkeypatch.setattr(
branch_audit,
"_load_diagnostics_checker",
lambda: None,
)
monkeypatch.setattr(branch_audit, "scan_branch", lambda p: None)
branch_audit.audit_branch(branch, [])
import_checked_names = {Path(c.args[0]).name for c in import_checker.check_module.call_args_list}
normal_checked_names = {Path(c.args[0]).name for c in normal_checker.check_module.call_args_list}
assert "__init__.py" in import_checked_names
assert "__init__.py" not in normal_checked_names
def test_dynamic_post_check(self, tmp_path, monkeypatch):
"""check_branch_post discovered and called."""
from aipass.seedgo.apps.handlers.audit import branch_audit
@@ -48,6 +48,10 @@ def _mock_infrastructure(monkeypatch):
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.json.json_handler", json_mod)
from aipass.seedgo.apps.handlers.bypass.utils import is_bypassed as real_is_bypassed
from aipass.seedgo.apps.handlers.bypass.ignore_handler import (
is_seedgo_ignored as real_is_seedgo_ignored,
load_ignore_entries as real_load_ignore_entries,
)
bypass_pkg = MagicMock()
bypass_utils = MagicMock()
@@ -55,6 +59,8 @@ def _mock_infrastructure(monkeypatch):
bypass_pkg.utils = bypass_utils
bypass_ignore = MagicMock()
bypass_ignore.get_template_ignore_patterns = MagicMock(return_value=[])
bypass_ignore.is_seedgo_ignored = real_is_seedgo_ignored
bypass_ignore.load_ignore_entries = real_load_ignore_entries
bypass_pkg.ignore_handler = bypass_ignore
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass", bypass_pkg)
monkeypatch.setitem(sys.modules, "aipass.seedgo.apps.handlers.bypass.utils", bypass_utils)