import contextlib import hashlib import importlib.util import io import os import tempfile import unittest from pathlib import Path ROOT = Path(__file__).resolve().parent.parent spec = importlib.util.spec_from_file_location("lean_install", ROOT / "scripts" / "lean-install.py") lean = importlib.util.module_from_spec(spec) spec.loader.exec_module(lean) SETTINGS = b'{"model": "opusplan", "unknownKey": {"keep": true}}\n' CONFIG = b'model = "x"\n[unknown]\nkeep = 1\n' AUTH = b'{"token": "not-a-real-secret"}\n' def run(*argv): out, err = io.StringIO(), io.StringIO() with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): code = lean.main(list(argv)) return code, out.getvalue(), err.getvalue() def digest(path): return hashlib.sha256(Path(path).read_bytes()).hexdigest() def make_homes(tmp): claude, codex = Path(tmp) / "claude", Path(tmp) / "codex" claude.mkdir() codex.mkdir() (claude / "settings.json").write_bytes(SETTINGS) (codex / "config.toml").write_bytes(CONFIG) (codex / "auth.json").write_bytes(AUTH) return claude, codex def global_args(claude, codex, *extra): return ["global", "--claude-home", str(claude), "--codex-home", str(codex), *extra] class GlobalInstallTest(unittest.TestCase): def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.addCleanup(self._tmp.cleanup) self.claude, self.codex = make_homes(self._tmp.name) def snapshot(self): return sorted((str(p), p.read_bytes()) for p in Path(self._tmp.name).rglob("*") if p.is_file()) def test_dry_run_writes_nothing(self): before = self.snapshot() code, out, _ = run(*global_args(self.claude, self.codex)) self.assertEqual(code, 0) self.assertIn("dry-run", out) self.assertEqual(before, self.snapshot()) def test_apply_installs_and_preserves_config_and_auth(self): code, _, _ = run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual(code, 0) self.assertEqual((self.claude / "settings.json").read_bytes(), SETTINGS) self.assertEqual((self.codex / "config.toml").read_bytes(), CONFIG) self.assertEqual((self.codex / "auth.json").read_bytes(), AUTH) for target in (self.claude / "CLAUDE.md", self.codex / "AGENTS.md"): self.assertIn(lean.BEGIN, target.read_text()) self.assertTrue((self.claude / "skills" / "lean-review" / "SKILL.md").is_file()) self.assertFalse((self.codex / "skills").exists()) def test_repeat_apply_is_idempotent_and_makes_no_backups(self): run(*global_args(self.claude, self.codex, "--apply")) before = self.snapshot() code, out, _ = run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual(code, 0) self.assertNotIn("backup", out) self.assertEqual(before, self.snapshot()) def test_existing_instructions_are_merged_with_backup(self): original = "# My rules\n\nBe kind.\n" (self.claude / "CLAUDE.md").write_text(original) run(*global_args(self.claude, self.codex, "--apply")) merged = (self.claude / "CLAUDE.md").read_text() self.assertTrue(merged.startswith(original.rstrip("\n"))) self.assertIn(lean.BEGIN, merged) backups = list(self.claude.glob("CLAUDE.md.bak-*")) self.assertEqual(len(backups), 1) self.assertEqual(backups[0].read_text(), original) def test_stale_block_is_replaced_and_user_text_kept(self): (self.codex / "AGENTS.md").write_text(f"before\n\n{lean.BEGIN}\nSTALE-MARK\n{lean.END}\n\nafter\n") run(*global_args(self.claude, self.codex, "--apply")) text = (self.codex / "AGENTS.md").read_text() self.assertNotIn("STALE-MARK", text) self.assertIn("before", text) self.assertIn("after", text) self.assertEqual(text.count(lean.BEGIN), 1) def test_backup_name_collision_never_overwrites_existing_backup(self): (self.claude / "CLAUDE.md").write_text("mine\n") now = lean.datetime(2026, 1, 1, 0, 0, 0) first = lean.backup_path(self.claude / "CLAUDE.md", now) first.write_text("earlier backup") second = lean.backup_path(self.claude / "CLAUDE.md", now) self.assertNotEqual(first, second) self.assertEqual(first.read_text(), "earlier backup") def test_uninstall_restores_original_content(self): original = "# Mine\n\ntext\n" (self.codex / "AGENTS.md").write_text(original) run(*global_args(self.claude, self.codex, "--apply")) run("uninstall", "--claude-home", str(self.claude), "--codex-home", str(self.codex), "--apply") self.assertEqual((self.codex / "AGENTS.md").read_text().strip(), original.strip()) self.assertNotIn(lean.BEGIN, (self.claude / "CLAUDE.md").read_text()) def test_symlinked_target_is_refused(self): real = Path(self._tmp.name) / "real.md" real.write_text("x") (self.claude / "CLAUDE.md").symlink_to(real) code, _, err = run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual(code, 2) self.assertIn("symlink", err) self.assertEqual(real.read_text(), "x") self.assertFalse((self.codex / "AGENTS.md").exists()) def test_differing_skill_needs_force_and_is_backed_up(self): skill = self.claude / "skills" / "lean-review" / "SKILL.md" skill.parent.mkdir(parents=True) skill.write_text("custom") code, _, err = run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual(code, 2) self.assertIn("--force", err) self.assertEqual(skill.read_text(), "custom") self.assertFalse((self.claude / "CLAUDE.md").exists()) run(*global_args(self.claude, self.codex, "--apply", "--force")) self.assertEqual(len(list(skill.parent.glob("SKILL.md.bak-*"))), 1) def test_malformed_markers_are_refused(self): (self.claude / "CLAUDE.md").write_text(f"{lean.BEGIN}\nno end\n") code, _, err = run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual(code, 2) self.assertIn("malformed", err) def test_missing_home_is_refused_not_created(self): missing = Path(self._tmp.name) / "nope" code, _, err = run(*global_args(self.claude, missing, "--apply")) self.assertEqual(code, 2) self.assertFalse(missing.exists()) self.assertIn("not an existing directory", err) def test_non_utf8_instruction_file_is_refused(self): (self.codex / "AGENTS.md").write_bytes(b"\xff\xfe\x00bad") code, _, err = run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual(code, 2) self.assertEqual((self.codex / "AGENTS.md").read_bytes(), b"\xff\xfe\x00bad") def test_codex_home_environment_is_honoured(self): os.environ["CODEX_HOME"] = str(self.codex) self.addCleanup(os.environ.pop, "CODEX_HOME", None) run("global", "--claude-home", str(self.claude), "--apply") self.assertTrue((self.codex / "AGENTS.md").is_file()) def test_existing_file_mode_is_preserved(self): (self.codex / "AGENTS.md").write_text("x\n") os.chmod(self.codex / "AGENTS.md", 0o640) run(*global_args(self.claude, self.codex, "--apply")) self.assertEqual((self.codex / "AGENTS.md").stat().st_mode & 0o777, 0o640) class ProjectInstallTest(unittest.TestCase): def setUp(self): self._tmp = tempfile.TemporaryDirectory() self.addCleanup(self._tmp.cleanup) self.project = Path(self._tmp.name) def test_installs_agents_and_claude_import(self): code, _, _ = run("project", str(self.project), "--apply") self.assertEqual(code, 0) self.assertEqual((self.project / "CLAUDE.md").read_text().strip(), "@AGENTS.md") self.assertTrue((self.project / "AGENTS.md").is_file()) def test_existing_project_file_is_not_overwritten_without_force(self): (self.project / "AGENTS.md").write_text("mine") code, _, err = run("project", str(self.project), "--apply") self.assertEqual(code, 2) self.assertEqual((self.project / "AGENTS.md").read_text(), "mine") self.assertFalse((self.project / "CLAUDE.md").exists()) run("project", str(self.project), "--apply", "--force") self.assertEqual(len(list(self.project.glob("AGENTS.md.bak-*"))), 1) def test_repeat_is_idempotent(self): run("project", str(self.project), "--apply") _, out, _ = run("project", str(self.project), "--apply") self.assertNotIn("update", out) self.assertEqual(list(self.project.glob("*.bak-*")), []) def test_invalid_target_is_refused(self): code, _, err = run("project", str(self.project / "missing"), "--apply") self.assertEqual(code, 2) class ShippedContentTest(unittest.TestCase): def test_shared_files_stay_lean(self): for rel, ceiling in [("lean/global/instructions.md", 25), ("lean/project/AGENTS.md", 30), ("AGENTS.md", 30)]: self.assertLessEqual(len((ROOT / rel).read_text().splitlines()), ceiling, rel) def test_claude_md_imports_agents_md(self): for rel in ("CLAUDE.md", "lean/project/CLAUDE.md"): self.assertEqual((ROOT / rel).read_text().strip(), "@AGENTS.md") def test_skill_has_frontmatter_and_no_write_tools(self): text = (ROOT / "lean/skills/lean-review/SKILL.md").read_text() self.assertTrue(text.startswith("---\nname: lean-review\n")) self.assertNotIn("Edit", text.split("---")[1]) self.assertNotIn("Write", text.split("---")[1]) def test_shipped_content_has_no_secret_like_values(self): for path in (ROOT / "lean").rglob("*.md"): self.assertNotRegex(path.read_text(), r"(?i)(sk-[a-z0-9]{10,}|AKIA[A-Z0-9]{12,}|https?://\d)") if __name__ == "__main__": unittest.main()