Files
AIPass/src/aipass/backup/tests/pattern_scan.py
T
f19f7ac12d feat: system-wide compliance push + 896 tests across 10 branches (#124)
* feat(seedgo): deep nesting bypasses, dead code cleanup, json structure compliance

Co-Authored-By: @seedgo <seedgo@aipass>

* feat(memory): seedgo certification: introspection fixes, subprocess bypasses, silent catch cleanup

Co-Authored-By: @memory <memory@aipass>

* feat(api): seedgo certification: 94%→97%, 31/33 standards at 100%

Co-Authored-By: @api <api@aipass>

* feat(seedgo): deep nesting 100%, limit 3→4, checker refactors, @ validation, bypass cleanup

Co-Authored-By: @seedgo <seedgo@aipass>

* feat: seedgo cert sprint — 10 branches dispatched, drone introspection rebuilt, system-wide compliance push

Session 49-50 cert sprint results:
- drone: introspection rebuilt (proper auto-discovery), silent_catch 92%→100%, overall 97%
- api: 94%→97%, json_handler fixed, PR #116
- backup: 93%→94%, json_handler load_template→inline
- memory: 88%→91%, introspection 79%→100%, 10 bypasses for subprocess files
- skills: 97%, json_structure→100%, introspection→100%
- spawn: 97%→99%, 32/34 standards at 100%
- ai_mail: 95%→97%, 12 unused functions removed, 32/34 at 100%
- seedgo: checker improvements (deep_nesting threshold 3→4, various fixes)
- drone: removed from _MODULE_REGISTRY (DPLAN-0053 consensus)
- commons: introspection bypasses (22 entries), python3→drone refs fixed
- trigger/cli/prax/daemon/flow/backup: various cert fixes

New DPLANs: 0053 (drone audit), 0054 (bypass tracker), 0055 (persistent git branches)
New FPLAN: 0134 (persistent citizen git branches — drone build)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(seedgo): audit display dynamic rendering, 176 unit tests, test coverage 6→93%

Co-Authored-By: @seedgo <seedgo@aipass>

* feat(memory): seedgo compliance: 92% → 96%, fixes + 70 bypasses

Co-Authored-By: @memory <memory@aipass>

* feat: night shift — compliance push, drone persistent branches + module routing, dead code cleanup

Autonomous night shift (DPLAN-0057). System avg 93% → 96%, all 14 branches 95%+.

Drone: persistent citizen/{name} branches (FPLAN-0134), module routing fix (FPLAN-0136),
19 logger.info→console.print across 6 modules, @ enforcement hints.

Compliance: backup 94→95%, daemon 94→95%, flow 93→96%, prax 94→96%, trigger 93→96%.
Prax: 27 dead functions removed, monitoring cleanup. Flow: dead code removal, bypass.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(memory): unit tests: 194 tests, 7 test files, 43% module coverage

Co-Authored-By: @memory <memory@aipass>

* feat(api): unit tests: 117 tests, 5 test files, 40% module coverage (3-round audit)

Co-Authored-By: @api <api@aipass>

* feat: system-wide compliance push + 896 tests across 10 branches

S51-S52 accumulated work:
- Stale cleanup: flow dead code removed (write_plan_outputs.py, 139 lines from process.py), trimmed command_parser/display/registry_ops
- Daemon refactor: scheduler_cron.py split (920→388 lines), new action_processor + plugin_processor handlers
- Seedgo checker fixes: architecture_check, json_structure_check, silent_catch_check, unused_function_check improved
- Seedgo cleanup: mock_standard_1 removed, bypass.py removed, bypass_handler trimmed
- Compliance: bypass.json updates across 8 branches, pytest.ini + conftest.py standardized
- Test dispatch: 46 new test files across ai_mail(4), backup(11), cli(4), daemon(4), flow(6), prax(6), trigger(4), commons(6)
- Small fixes: drone lock_handler, json_handlers, prax operations/event_queue, commons writers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: @seedgo <seedgo@aipass>
Co-authored-by: @memory <memory@aipass>
Co-authored-by: @api <api@aipass>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:57:54 -07:00

169 lines
5.7 KiB
Python

# =================== AIPass ====================
# Name: test_pattern_scan.py
# Description: Pattern audit — scan home dir, report what passes ignore patterns
# Version: 1.0.0
# Created: 2026-03-14
# Modified: 2026-03-14
# =============================================
"""
Pattern Audit Scan
Diagnostic tool — uses the same ignore patterns and scanner as the real backup
to show what would be backed up. Reports file counts and sizes per top-level
directory, flags large files and long paths.
Usage:
python3 tests/test_pattern_scan.py
"""
from collections import defaultdict
from pathlib import Path
from rich.console import Console
from aipass.backup.apps.handlers.config.config_handler import (
GLOBAL_IGNORE_PATTERNS,
IGNORE_EXCEPTIONS,
should_ignore,
SOURCE_WHITELIST,
MAX_FILE_SIZE_MB,
)
from aipass.backup.apps.handlers.operations.file_scanner import scan_files
console = Console()
def fmt_size(b: int) -> str:
if b >= 1024 * 1024 * 1024:
return f"{b / (1024**3):.1f} GB"
if b >= 1024 * 1024:
return f"{b / (1024**2):.1f} MB"
if b >= 1024:
return f"{b / 1024:.1f} KB"
return f"{b} B"
def run_scan() -> None:
source_dir = Path.home()
large_file_threshold = 1 * 1024 * 1024 # 1MB
console.print()
console.print("[bold cyan]Pattern Audit Scan[/bold cyan]")
console.print(f" Source: {source_dir}")
console.print(f" Whitelist: {SOURCE_WHITELIST if SOURCE_WHITELIST else '(all directories)'}")
console.print(f" Max file size: {MAX_FILE_SIZE_MB} MB")
console.print(f" Large file threshold: {fmt_size(large_file_threshold)}")
console.print()
console.print("[dim]Scanning with current ignore patterns...[/dim]")
ignore_patterns = GLOBAL_IGNORE_PATTERNS
def check_ignore(path: Path) -> bool:
return should_ignore(path, ignore_patterns, IGNORE_EXCEPTIONS)
files, skipped = scan_files(source_dir, check_ignore,
whitelist=SOURCE_WHITELIST,
max_file_size_mb=MAX_FILE_SIZE_MB)
# Aggregate by top-level directory
dir_stats: dict[str, dict] = defaultdict(lambda: {"count": 0, "size": 0})
large_files: list = []
total_size = 0
path_too_long: list = []
for f in files:
try:
rel = f.relative_to(source_dir)
top_dir = rel.parts[0] if len(rel.parts) > 1 else "(root files)"
size = f.stat().st_size
dir_stats[top_dir]["count"] += 1
dir_stats[top_dir]["size"] += size
total_size += size
if size >= large_file_threshold:
large_files.append((f, size))
# Estimate backup path length
estimated_path = len(str(f)) + 80
if estimated_path > 260:
path_too_long.append((f, estimated_path))
except (OSError, ValueError):
pass
sorted_dirs = sorted(dir_stats.items(), key=lambda x: x[1]["size"], reverse=True)
# Report: directories
console.print()
console.print(f"[bold cyan]Files passing ignore patterns: {len(files)}[/bold cyan]")
console.print(f"[bold cyan]Total size: {fmt_size(total_size)}[/bold cyan]")
console.print(
f"[dim]Directories ignored: {len(skipped.get('directories', set()))} "
f"| Files ignored: {len(skipped.get('files', set()))}[/dim]"
)
console.print()
console.print("[yellow]Top directories by size:[/yellow]")
for dir_name, stats in sorted_dirs[:25]:
pct = (stats["size"] / total_size * 100) if total_size > 0 else 0
size_str = fmt_size(stats["size"])
color = "red" if pct > 20 else "yellow" if pct > 5 else "dim"
console.print(
f" [{color}]{dir_name:<40} {stats['count']:>6} files "
f"{size_str:>10} ({pct:.1f}%)[/{color}]"
)
if len(sorted_dirs) > 25:
console.print(f" [dim]... and {len(sorted_dirs) - 25} more directories[/dim]")
# Report: large files
if large_files:
large_files.sort(key=lambda x: x[1], reverse=True)
console.print()
console.print(
f"[yellow]Large files (>{fmt_size(large_file_threshold)}):[/yellow] "
f"{len(large_files)} found"
)
for f, size in large_files[:20]:
rel = f.relative_to(source_dir)
console.print(f" [red]{fmt_size(size):>10}[/red] {rel}")
if len(large_files) > 20:
console.print(f" [dim]... and {len(large_files) - 20} more[/dim]")
# Report: path too long
if path_too_long:
console.print()
console.print(
f"[yellow]Path too long (>260 chars estimated):[/yellow] "
f"{len(path_too_long)} found"
)
for f, length in path_too_long[:10]:
rel = f.relative_to(source_dir)
console.print(f" [red]{length} chars[/red] {rel}")
if len(path_too_long) > 10:
console.print(f" [dim]... and {len(path_too_long) - 10} more[/dim]")
# Report: files skipped by size cap
too_large = skipped.get("too_large", set())
if too_large:
sorted_large = sorted(too_large, key=lambda x: x[1], reverse=True)
console.print()
console.print(
f"[yellow]Skipped by size cap (>{MAX_FILE_SIZE_MB} MB):[/yellow] "
f"{len(sorted_large)} files"
)
for rel_path, size in sorted_large[:20]:
console.print(f" [red]{fmt_size(size):>10}[/red] {rel_path}")
if len(sorted_large) > 20:
console.print(f" [dim]... and {len(sorted_large) - 20} more[/dim]")
if not large_files and not path_too_long and not too_large:
console.print()
console.print("[green]No large files or long paths detected[/green]")
console.print()
if __name__ == "__main__":
run_scan()