fix(init): auto-add per-project files to .gitignore in git repos (#185) (#866)

Partially addresses #185.

`mempalace init <dir>` writes `mempalace.yaml` and `entities.json` into
the project root. When <dir> is a git repository, those files have no
default protection and risk being committed by accident — the loudest
concern in the original report.

This PR adds `_ensure_mempalace_files_gitignored()` which runs at the
end of cmd_init: if <dir>/.git exists, append the two filenames to
.gitignore (creating it if necessary) under a clearly-marked block.

The helper is conservative:
- only runs when <dir>/.git is present (no-op for non-git projects)
- skips entries already present (no duplicates)
- preserves existing .gitignore content
- handles files without trailing newlines

This does NOT relocate the files to ~/.mempalace/wings/<wing>/ as the
issue's 'Expected' section proposes — that's a behavioral change with
miner/config implications and warrants a separate design discussion.
The gitignore safeguard removes the immediate risk without breaking any
existing flow.

Tests: 5 cases in tests/test_init_gitignore_protection.py covering
no-op, fresh creation, partial append, idempotency, and missing-newline
edge case.
This commit is contained in:
Arnold Wender
2026-04-15 09:26:41 +02:00
committed by GitHub
parent 6a73eb2e20
commit 0aee6f3ed9
2 changed files with 96 additions and 0 deletions
+34
View File
@@ -36,6 +36,37 @@ from pathlib import Path
from .config import MempalaceConfig
_MEMPALACE_PROJECT_FILES = ("mempalace.yaml", "entities.json")
def _ensure_mempalace_files_gitignored(project_dir) -> bool:
"""If project_dir is a git repo, ensure MemPalace's per-project files
are listed in .gitignore so they don't get committed by accident.
Returns True if .gitignore was updated, False otherwise. Issue #185:
`mempalace init` writes mempalace.yaml + entities.json into the
project root, where they previously had no protection against being
staged into git.
"""
from pathlib import Path
project_path = Path(project_dir).expanduser().resolve()
if not (project_path / ".git").exists():
return False
gitignore = project_path / ".gitignore"
existing = gitignore.read_text() if gitignore.exists() else ""
existing_lines = {line.strip() for line in existing.splitlines()}
missing = [p for p in _MEMPALACE_PROJECT_FILES if p not in existing_lines]
if not missing:
return False
prefix = "" if not existing or existing.endswith("\n") else "\n"
block = prefix + "\n# MemPalace per-project files (issue #185)\n" + "\n".join(missing) + "\n"
with open(gitignore, "a") as f:
f.write(block)
print(f" Added {', '.join(missing)} to {gitignore.name}")
return True
def cmd_init(args):
import json
from pathlib import Path
@@ -64,6 +95,9 @@ def cmd_init(args):
detect_rooms_local(project_dir=args.dir, yes=getattr(args, "yes", False))
MempalaceConfig().init()
# Pass 3: protect git repos from accidentally committing per-project files
_ensure_mempalace_files_gitignored(args.dir)
def cmd_mine(args):
palace_path = os.path.expanduser(args.palace) if args.palace else MempalaceConfig().palace_path