diff --git a/scripts/playbook.py b/scripts/playbook.py index ef3656f..51d001c 100644 --- a/scripts/playbook.py +++ b/scripts/playbook.py @@ -2,7 +2,7 @@ import sys from datetime import datetime, timezone from pathlib import Path -from shutil import copy2, copytree, which +from shutil import copy2, copytree, rmtree, which import subprocess try: @@ -683,8 +683,9 @@ def sync_rules_action(config: dict, context: dict) -> int: main_language = resolve_main_language(config, context) playbook_scripts = resolve_playbook_scripts(project_root, context) date_value = config.get("date") or datetime.now().strftime("%Y-%m-%d") + no_backup = bool(config.get("no_backup", False)) - backup_path(rules_dst, False) + backup_path(rules_dst, no_backup) text = rules_src.read_text(encoding="utf-8") text = replace_placeholders( text, project_name, date_value, main_language, playbook_scripts @@ -879,16 +880,18 @@ def read_gitattributes_entries(path: Path) -> list[str]: return entries -def sync_gitattributes_overwrite(src: Path, dst: Path) -> None: +def sync_gitattributes_overwrite(src: Path, dst: Path, no_backup: bool) -> None: if src.resolve() == dst.resolve(): log("Skip: .gitattributes source equals destination.") return - backup_path(dst, False) + backup_path(dst, no_backup) copy2(src, dst) log("Synced .gitattributes from standards (overwrite).") -def sync_gitattributes_append(src: Path, dst: Path, source_note: str) -> None: +def sync_gitattributes_append( + src: Path, dst: Path, source_note: str, no_backup: bool +) -> None: src_entries = read_gitattributes_entries(src) dst_entries: list[str] = [] if dst.exists(): @@ -899,7 +902,7 @@ def sync_gitattributes_append(src: Path, dst: Path, source_note: str) -> None: return original = dst.read_text(encoding="utf-8") if dst.exists() else "" - backup_path(dst, False) + backup_path(dst, no_backup) header = f"# Added from playbook .gitattributes (source: {source_note})" content = original.rstrip("\n") if content: @@ -909,7 +912,7 @@ def sync_gitattributes_append(src: Path, dst: Path, source_note: str) -> None: log("Appended missing .gitattributes rules from standards.") -def sync_gitattributes_block(src: Path, dst: Path) -> None: +def sync_gitattributes_block(src: Path, dst: Path, no_backup: bool) -> None: begin = "# BEGIN playbook .gitattributes" end = "# END playbook .gitattributes" begin_old = "# BEGIN tsl-playbook .gitattributes" @@ -939,7 +942,7 @@ def sync_gitattributes_block(src: Path, dst: Path) -> None: if updated and updated[-1].strip(): updated.append("") updated.extend(block_lines) - backup_path(dst, False) + backup_path(dst, no_backup) dst.write_text("\n".join(updated) + "\n", encoding="utf-8") else: dst.write_text("\n".join(block_lines) + "\n", encoding="utf-8") @@ -960,6 +963,7 @@ def sync_standards_action(config: dict, context: dict) -> int: agents_root = project_root / ".agents" ensure_dir(agents_root) + no_backup = bool(config.get("no_backup", False)) timestamp = datetime.now().strftime("%Y%m%d%H%M%S") for lang in langs: src = PLAYBOOK_ROOT / "rulesets" / lang @@ -968,9 +972,12 @@ def sync_standards_action(config: dict, context: dict) -> int: return 2 dst = agents_root / lang if dst.exists(): - backup = agents_root / f"{lang}.bak.{timestamp}" - dst.rename(backup) - log(f"Backed up existing {lang} agents -> {backup.name}") + if no_backup: + rmtree(dst) + else: + backup = agents_root / f"{lang}.bak.{timestamp}" + dst.rename(backup) + log(f"Backed up existing {lang} agents -> {backup.name}") copytree(src, dst) log(f"Synced .agents/{lang} from standards.") @@ -1005,11 +1012,13 @@ def sync_standards_action(config: dict, context: dict) -> int: if mode == "skip": log("Skip: .gitattributes sync (mode=skip).") elif mode == "overwrite": - sync_gitattributes_overwrite(gitattributes_src, gitattributes_dst) + sync_gitattributes_overwrite(gitattributes_src, gitattributes_dst, no_backup) elif mode == "block": - sync_gitattributes_block(gitattributes_src, gitattributes_dst) + sync_gitattributes_block(gitattributes_src, gitattributes_dst, no_backup) else: - sync_gitattributes_append(gitattributes_src, gitattributes_dst, source_note) + sync_gitattributes_append( + gitattributes_src, gitattributes_dst, source_note, no_backup + ) return 0 diff --git a/tests/test_no_backup_flags.py b/tests/test_no_backup_flags.py new file mode 100644 index 0000000..4f23dff --- /dev/null +++ b/tests/test_no_backup_flags.py @@ -0,0 +1,76 @@ +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "playbook.py" + + +def run_cli(*args): + return subprocess.run( + [sys.executable, str(SCRIPT), *args], + capture_output=True, + text=True, + ) + + +class NoBackupFlagsTests(unittest.TestCase): + def test_sync_rules_no_backup_skips_backup_file(self): + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + rules = root / "AGENT_RULES.md" + rules.write_text("old rules", encoding="utf-8") + + config_body = f""" +[playbook] +project_root = "{tmp_dir}" + +[sync_rules] +force = true +no_backup = true +""" + config_path = root / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_cli("-config", str(config_path)) + self.assertEqual(result.returncode, 0, msg=result.stderr) + + backups = list(root.glob("AGENT_RULES.md.bak.*")) + self.assertEqual(backups, []) + self.assertTrue(rules.is_file()) + + def test_sync_standards_no_backup_skips_agents_and_gitattributes_backup(self): + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + agents = root / ".agents" / "tsl" + agents.mkdir(parents=True) + (agents / "index.md").write_text("old", encoding="utf-8") + + gitattributes = root / ".gitattributes" + gitattributes.write_text("*.txt text\n", encoding="utf-8") + + config_body = f""" +[playbook] +project_root = "{tmp_dir}" + +[sync_standards] +langs = ["tsl"] +gitattr_mode = "append" +no_backup = true +""" + config_path = root / "playbook.toml" + config_path.write_text(config_body, encoding="utf-8") + + result = run_cli("-config", str(config_path)) + self.assertEqual(result.returncode, 0, msg=result.stderr) + + agents_backups = list((root / ".agents").glob("tsl.bak.*")) + self.assertEqual(agents_backups, []) + git_backups = list(root.glob(".gitattributes.bak.*")) + self.assertEqual(git_backups, []) + + +if __name__ == "__main__": + unittest.main()