From 7191ae9e62c5b2a96d2f6482baf72197b52139fc Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Tue, 21 Apr 2026 23:44:45 +0800 Subject: [PATCH] feat(setup): add safety harness - never overwrite user files - Add safe_copy() function: checks if file exists, skips if true - Auto-backup existing files before any modification - Safety notice at install start - Protects Zotero DB, existing notes, and configs --- scripts/setup.py | 57 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/scripts/setup.py b/scripts/setup.py index 0c24c550..94ea017f 100644 --- a/scripts/setup.py +++ b/scripts/setup.py @@ -429,6 +429,26 @@ Generated by PaperForge setup.py on {platform.system()} print_success(f"AGENTS.md created at {agents_path}") +def safe_copy(src: Path, dst: Path, backup: bool = True) -> bool: + """Safely copy file, never overwrite without consent. + + Returns True if copied, False if skipped. + """ + if dst.exists(): + if backup: + backup_path = dst.with_suffix(dst.suffix + ".backup") + shutil.copy2(dst, backup_path) + print(f"{Colors.YELLOW}[BACKUP]{Colors.ENDC} Existing file saved to {backup_path.name}") + + print(f"{Colors.YELLOW}[SKIP]{Colors.ENDC} File exists: {dst}") + print(f" To update, manually delete or rename the existing file.") + return False + + dst.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(src, dst) + return True + + def deploy_workflow_scripts(vault_path: Path, agent_key: str, agent_config: dict, paths: dict) -> bool: """Deploy workflow scripts from repo to vault. @@ -471,13 +491,11 @@ def deploy_workflow_scripts(vault_path: Path, agent_key: str, agent_config: dict continue try: - # Ensure parent directory exists - dst_path.parent.mkdir(parents=True, exist_ok=True) - - # Copy file - shutil.copy2(src_path, dst_path) - print_success(f"Deployed: {dst_rel}") - success_count += 1 + if safe_copy(src_path, dst_path): + print_success(f"Deployed: {dst_rel}") + success_count += 1 + else: + fail_count += 1 except Exception as e: print_error(f"Failed to deploy {src_rel}: {e}") fail_count += 1 @@ -493,8 +511,10 @@ def deploy_workflow_scripts(vault_path: Path, agent_key: str, agent_config: dict for chart_file in chart_files: try: dst_file = chart_guide_dst / chart_file.name - shutil.copy2(chart_file, dst_file) - success_count += 1 + if safe_copy(chart_file, dst_file): + success_count += 1 + else: + fail_count += 1 except Exception as e: print_error(f"Failed to deploy chart guide {chart_file.name}: {e}") fail_count += 1 @@ -511,13 +531,10 @@ def deploy_workflow_scripts(vault_path: Path, agent_key: str, agent_config: dict dst_path = vault_path / dst_rel if src_path.exists(): - try: - dst_path.parent.mkdir(parents=True, exist_ok=True) - shutil.copy2(src_path, dst_path) + if safe_copy(src_path, dst_path): print_success(f"Deployed template: {src_rel}") success_count += 1 - except Exception as e: - print_error(f"Failed to deploy template {src_rel}: {e}") + else: fail_count += 1 else: print_warning(f"Template not found (skipping): {src_rel}") @@ -534,8 +551,10 @@ def deploy_workflow_scripts(vault_path: Path, agent_key: str, agent_config: dict for cmd_file in command_files: try: dst_file = command_dst / cmd_file.name - shutil.copy2(cmd_file, dst_file) - success_count += 1 + if safe_copy(cmd_file, dst_file): + success_count += 1 + else: + fail_count += 1 except Exception as e: print_error(f"Failed to deploy command {cmd_file.name}: {e}") fail_count += 1 @@ -595,7 +614,11 @@ def main() -> int: except ImportError: print_header("Literature Workflow Installer") - print("This script will help you configure the literature research pipeline.\n") + print("This script will help you configure the literature research pipeline.") + print(f"\n{Colors.BRIGHT_YELLOW}{Colors.BOLD}[SAFETY NOTICE]{Colors.ENDC}") + print(f" - This installer will NOT modify your Zotero database or existing notes") + print(f" - Existing files will be backed up (.backup) before any change") + print(f" - You can safely re-run this script; it will skip existing files\n") # Step 0: Select agent platform agent_key, agent_config = select_agent()