Write file content. - WRITE_PROTECTED files (e.g. .gitignore, README.md) always require explicit confirmation when the file already exists. - All other existing files require confirmation when AGENT_CONFIG confirm_write is True. Args: path: Path to file
(path: str, content: str)
| 81 | |
| 82 | |
| 83 | def tool_write_file(path: str, content: str) -> str: |
| 84 | """ |
| 85 | Write file content. |
| 86 | |
| 87 | - WRITE_PROTECTED files (e.g. .gitignore, README.md) always require |
| 88 | explicit confirmation when the file already exists. |
| 89 | - All other existing files require confirmation when AGENT_CONFIG |
| 90 | confirm_write is True. |
| 91 | |
| 92 | Args: |
| 93 | path: Path to file |
| 94 | content: Content to write |
| 95 | |
| 96 | Returns: |
| 97 | Success message or error message |
| 98 | """ |
| 99 | from utils.logger import confirm as ask_confirm, warning as log_warning |
| 100 | |
| 101 | p = Path(path) |
| 102 | if not p.is_absolute(): |
| 103 | import os |
| 104 | p = Path(os.getcwd()) / path |
| 105 | file_exists = p.exists() and p.is_file() |
| 106 | |
| 107 | # Block writes that would replace a file with drastically smaller content |
| 108 | # (e.g., overwriting 500-line app.py with just a shebang line) |
| 109 | if file_exists and p.suffix in ('.py', '.js', '.ts', '.html', '.css'): |
| 110 | try: |
| 111 | existing_size = p.stat().st_size |
| 112 | new_size = len(content.encode('utf-8')) |
| 113 | # If existing file is > 200 bytes and new content is < 20% of it, block |
| 114 | if existing_size > 200 and new_size < existing_size * 0.2: |
| 115 | return ( |
| 116 | f"[ERROR] Refusing to overwrite {p.name} ({existing_size} bytes) " |
| 117 | f"with much smaller content ({new_size} bytes). " |
| 118 | f"This looks like a stub or incomplete rewrite. " |
| 119 | f"Write the COMPLETE file content, or use patch_file for small edits." |
| 120 | ) |
| 121 | except Exception: |
| 122 | pass |
| 123 | |
| 124 | # Block writes to binary/non-text file types — these must be created by code. |
| 125 | if p.suffix.lower() in BINARY_FILE_TYPES: |
| 126 | hint = "" |
| 127 | if p.suffix.lower() in ('.db', '.sqlite', '.sqlite3'): |
| 128 | hint = ( |
| 129 | " SQLite databases are created automatically when your Python code " |
| 130 | "calls sqlite3.connect(). Do NOT create them with write_file — " |
| 131 | "just use sqlite3.connect() in your app code and the file appears on first run." |
| 132 | ) |
| 133 | return f"[ERROR] Cannot write {p.name} as a text file.{hint}" |
| 134 | |
| 135 | # Protected files: always confirm before overwriting. |
| 136 | if file_exists and p.name in WRITE_PROTECTED: |
| 137 | log_warning(f"Attempting to overwrite protected file: {path}") |
| 138 | if not ask_confirm(f"Really overwrite {p.name}?"): |
| 139 | return f"[CANCELLED] Overwrite of {path} cancelled." |
| 140 |