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
This commit is contained in:
Research Assistant 2026-04-21 23:44:45 +08:00
parent 68f65ea69a
commit 7191ae9e62

View file

@ -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()