Preview AI File-Renaming Plans Before Touching Files
An AI assistant can turn vague filenames into a useful naming plan. It can also suggest two identical destinations or a name that overwrites an existing file. Ask for a proposal, not a script with permission to change your folder.
This tutorial builds a read-only Python checker for small JSON rename plans. It works offline on a case-sensitive Linux filesystem; no model account or additional packages are needed. The examples are synthetic, not measured AI outputs.
1. Separate the proposal from execution
Give your assistant an approved filename inventory and this contract:
- Return only a JSON list of objects containing
sourceandtarget. - Use basenames, never directory paths. Names must start with an ASCII letter or digit, contain only ASCII letters, digits, dots, underscores, or hyphens, and be at most 100 characters.
- Each source must be an existing regular file, not a symbolic link. Use each source and destination only once.
- Never target an occupied name, even if another row proposes moving that file away.
This deliberately excludes swaps, chains, hidden files, and names containing spaces. Keep private filenames out of unapproved services. If you change the naming rule, add regex acceptance tests first.
Do not test an AI-written renaming loop against originals. On Unix, Path.rename() can overwrite an existing file when permissions allow it. Python documents that platform-dependent behavior.
2. Save the read-only checker
Save this as audit_renames.py in a fresh working directory. Use Python 3.10 or later. It validates the whole plan before printing any preview and contains no rename or write operation.
import json
from pathlib import Path
import re
import sys
NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,99}")
def audit(root, plan_path):
with open(plan_path, encoding="utf-8") as stream:
plan = json.load(stream)
if not isinstance(plan, list) or not plan:
raise ValueError("plan must be a nonempty list")
occupied = {entry.name for entry in root.iterdir()}
sources, targets = set(), set()
for row in plan:
if not isinstance(row, dict) or set(row) != {"source", "target"}:
raise ValueError("each row needs only source and target")
source, target = row["source"], row["target"]
if any(not isinstance(n, str) or not NAME.fullmatch(n)
for n in (source, target)):
raise ValueError("names must be simple ASCII basenames")
original = root / source
if original.is_symlink() or not original.is_file():
raise ValueError(f"not a regular source file: {source}")
if source in sources:
raise ValueError(f"repeated source: {source}")
if target in occupied:
raise ValueError(f"target already exists: {target}")
if target in targets:
raise ValueError(f"duplicate target: {target}")
sources.add(source)
targets.add(target)
return plan
if __name__ == "__main__":
if len(sys.argv) != 3:
raise SystemExit("Usage: python3 audit_renames.py DIRECTORY PLAN.json")
try:
plan = audit(Path(sys.argv[1]), sys.argv[2])
except (OSError, ValueError) as error:
raise SystemExit(f"FAIL: {error}")
for row in plan:
print(f"PREVIEW {row['source']} -> {row['target']}")
print(f"PASS: {len(plan)} planned renames; no files changed")
The standard-library JSON decoder reads data, not executable Python. Parsing alone does not enforce our naming or collision rules; the loop supplies those checks.
3. Make a disposable fixture
Save this as make_fixture.py, beside the checker. Run it only in the fresh working directory: it creates three text files, one local symbolic link, and seven plans. These plans deliberately exercise different outcomes.
import json
from pathlib import Path
root = Path("rename-demo")
root.mkdir()
for name in ("report-01.txt", "report-02.txt", "archive.txt"):
(root / name).write_text(name + "\n", encoding="utf-8")
(root / "shortcut.txt").symlink_to("report-01.txt")
def row(source, target):
return {"source": source, "target": target}
plans = {
"good": [row("report-01.txt", "notes-01.txt"),
row("report-02.txt", "notes-02.txt")],
"collision": [row("report-01.txt", "notes.txt"),
row("report-02.txt", "notes.txt")],
"occupied": [row("report-01.txt", "archive.txt")],
"escape": [row("report-01.txt", "../notes.txt")],
"missing": [row("report-99.txt", "notes.txt")],
"repeated": [row("report-01.txt", "notes-01.txt"),
row("report-01.txt", "notes-02.txt")],
"symlink": [row("shortcut.txt", "notes.txt")],
}
for name, plan in plans.items():
Path(name + ".json").write_text(json.dumps(plan), encoding="utf-8")
The fixture refuses to reuse an existing rename-demo directory. It does not need your real documents or an AI-generated response.
4. Run positive and negative checks
In Bash, run:
python3 make_fixture.py
for plan in good collision occupied escape missing repeated symlink; do
python3 audit_renames.py rename-demo "$plan.json"
echo "exit=$?"
done
Do not enable set -e for this demonstration: six failures are intentional. The good plan prints:
PREVIEW report-01.txt -> notes-01.txt
PREVIEW report-02.txt -> notes-02.txt
PASS: 2 planned renames; no files changed
Locally verified with Python 3.11.4:
| Plan | Result | Exit |
|---|---|---|
good |
Three stdout lines above; empty stderr | 0 |
collision |
FAIL: duplicate target: notes.txt |
1 |
occupied |
FAIL: target already exists: archive.txt |
1 |
escape |
FAIL: names must be simple ASCII basenames |
1 |
missing |
FAIL: not a regular source file: report-99.txt |
1 |
repeated |
FAIL: repeated source: report-01.txt |
1 |
symlink |
FAIL: not a regular source file: shortcut.txt |
1 |
All failing results go to stderr with empty stdout. The Bash loop prints each exit status separately. File names, contents, and the link target remained unchanged after all seven runs. As with testing AI-written SQL, deliberate failures check that the checker actually rejects mistakes.
5. Troubleshoot and respect the limits
- Invalid JSON: remove Markdown wrappers and commentary. Do not execute the response to repair it.
- Occupied or duplicate target: ask for a new destination. Never bypass the check by deleting the existing file.
- Missing source: refresh the inventory rather than guessing which document was intended.
- Spaces or accented names rejected: this is the narrow tutorial contract, not a filesystem limitation. Extend the policy and fixtures explicitly.
Passing means the proposal meets these checks, not that its names describe the documents correctly. Review meaning and extensions yourself. The inventory is a momentary view, not a filesystem lock; directory changes can invalidate it. Python’s directory-iteration reference explains that concurrent additions and removals have unspecified inclusion.
This is not a production renaming tool or a portable collision detector for Windows and case-insensitive volumes. Keep execution separate, back up originals, and test any future executor on copies with its own no-overwrite and recovery controls.
Practical takeaway
Let AI propose names while deterministic checks reject broken plans. Keep the accepted plan and its result, then add each new failure to your continuous-testing fixtures. A preview is useful precisely because it changes nothing.
Related articles
Lack of Agreement on AI Rules in U.S., EU Gives China a Leg Up
15 Best AI Tools for Startup Founders
How AI Can Be Your HR Sidekick in Recruitment and Employee Engagement
AI Call Center Is No Longer a Myth but a Reality: 6 Steps for Painless Implementation
How to Use AI Brand Voice Generator to Preserve Channel-Specific Voices
Lightweight Agno Framework for Fast Multimodal AI Agent Creation
Your Easy-to-Follow Guide to Agentic AI Workflows
Anthropic's Public Benefit Fund: What Its 5% Share Pledge Means
AWS Seeks to Teach Executives About Generative AI
Top 11 Companies Hiring for AI Jobs in 2025
Job Displacement Due to AI: Risks, Solutions, and Future Workforce Strategies
12 Top Resources to Build an Ethical AI Framework
Latest articles
Test AI-Generated Regex with Small Python Fixtures
Test AI-Generated SQL with Small SQLite Fixtures
Evaluate AI Transcription Accuracy with Word Error Rate
Evaluate AI Ticket Routing With a Confusion Matrix
How to Evaluate RAG Retrieval With Recall@k and MRR
How to Evaluate AI Agents Before Deployment
AI Agent Memory vs. RAG: What to Store and What to Retrieve
Anthropic's Public Benefit Fund: What Its 5% Share Pledge Means
Giving Robots a Sense of Touch: Tactile Sensors Reach the Fingertips
BT Highlights the Crucial Role of Networks in AI at Mobile World Congress
The Beginner’s Guide to Building RDDs in Apache Spark with PySpark