diff --git a/CHANGELOG.md b/CHANGELOG.md index 98f81ce4..db243ca9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,16 @@ PyPI version — not the changelog header. a step to audit every open todo against the actual system (`ls`/`find`/`git ls-files`/`grep`/`audit`) and close what's verifiably done — catching todos finished in a past session but never closed. +- **Backup ignore architecture documented** — confirmed and written down the + two-layer model so it stops getting re-discovered: `BUILTIN_IGNORES` is the + **seed** that generates a new project's `.backupignore` at register and is + never consulted at backup time; `.backupignore` (via `load_spec`) is the + **runtime source of truth**. There's no static fallback, so the seed is + safety-critical — an empty `.backupignore` backs up everything and can crash + the machine. Added a "How Ignores Work" README section + code comments on + `BUILTIN_IGNORES` and `load_spec`. Also added `logs/` to the seed so new + projects exclude log directories (e.g. prax `.jsonl` output) by default, not + just `*.log` files (td-27). ## [2026-06-23] diff --git a/src/aipass/backup/README.md b/src/aipass/backup/README.md index c130fadf..000dadb2 100644 --- a/src/aipass/backup/README.md +++ b/src/aipass/backup/README.md @@ -105,15 +105,17 @@ The root `.gitignore` covers all three with a single `.backup/` entry. --- -## `.backupignore` +## How Ignores Work -A true `.gitignore` for backups, using real pathspec/gitwildmatch semantics: -- `#` comments, blank lines ignored -- `!` negation (un-ignore a path) -- Trailing `/` for directory-only matching -- Last-match-wins ordering +Two layers — seed and runtime: -Lives at the **project root** and is the single source of truth governing snapshot, versioned, Drive sync, and mirror-cleanup operations. Generated from `BUILTIN_IGNORES` in `handlers/ignore/patterns.py` during `register`. The `.backup/` directory is included in `BUILTIN_IGNORES`, so the store self-excludes from its own backups. +1. **`BUILTIN_IGNORES`** (`handlers/ignore/patterns.py`) — the **seed**. Written into a new project's `.backupignore` at `register` time (`setup._build_backupignore()`). Never consulted at backup time. +2. **`.backupignore`** — the **runtime source of truth**. `load_spec()` reads it on every backup; `BUILTIN_IGNORES` is not applied. True pathspec/gitwildmatch semantics: `#` comments, `!` negation, trailing `/` for dirs, last-match-wins. + +There is no static fallback. The seed IS the safety mechanism — an empty or missing `.backupignore` means back up everything (`.venv`, `node_modules`, `.git`), which can crash the machine. Keep `BUILTIN_IGNORES` sane. + +- To change defaults for **new** projects → edit `BUILTIN_IGNORES` in `patterns.py` +- To change ignores for an **existing** project → edit its `.backupignore` The repo-root `/.backupignore` ships intentionally as the curated default so users don't snapshot junk. diff --git a/src/aipass/backup/apps/handlers/ignore/patterns.py b/src/aipass/backup/apps/handlers/ignore/patterns.py index f77ad88a..f85876c8 100644 --- a/src/aipass/backup/apps/handlers/ignore/patterns.py +++ b/src/aipass/backup/apps/handlers/ignore/patterns.py @@ -17,6 +17,9 @@ import pathspec from ..json import json_handler from ..path import builder +# Seed for new-project .backupignore (written at register by setup._build_backupignore). +# NOT applied at backup time — runtime ignores come from .backupignore via load_spec(). +# Keep sane: empty seed = back up everything = crash. BUILTIN_IGNORES = [ ".backup/", ".git/", @@ -40,6 +43,7 @@ BUILTIN_IGNORES = [ "build/", "dist/", "*.log", + "logs/", ".ruff_cache/", ".coverage", ] @@ -48,6 +52,7 @@ BUILTIN_IGNORES = [ def load_spec(project_root: str) -> pathspec.PathSpec: """Load a PathSpec from .backupignore at the project root. + This is the runtime source of truth — BUILTIN_IGNORES is not consulted here. Reads raw lines — pathspec handles #comments, blanks, !negation, anchoring, dir-only trailing /, and last-match-wins natively. diff --git a/src/aipass/backup/tests/test_ignore_pathspec.py b/src/aipass/backup/tests/test_ignore_pathspec.py index d96b9d25..6b0a03bc 100644 --- a/src/aipass/backup/tests/test_ignore_pathspec.py +++ b/src/aipass/backup/tests/test_ignore_pathspec.py @@ -250,6 +250,10 @@ class TestSeedTemplate: """Seed defaults include .coverage.""" assert ".coverage" in BUILTIN_IGNORES + def test_builtin_has_logs_dir(self): + """Seed defaults exclude logs/ directories.""" + assert "logs/" in BUILTIN_IGNORES + def test_build_backupignore_content(self): """Seed template includes ruff_cache, coverage, and pycache.""" from aipass.backup.apps.handlers.project.setup import _build_backupignore