# Skill

> Rename by content

- Skill: `meturley/skill` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add meturley/skill`
- Raw SKILL.md: https://api.skillmd.com/api/skills/meturley/skill/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: meturley (https://skillmd.com/u/meturley)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/meturley/skill

---


# Rename by content

Someone drags in a folder of files named `IMG_4821.jpg`, `Scan 12.pdf`, `doc(3).docx`. The
information needed to name them well is sitting *inside* each file. The job is to get it out,
shape it into names that match how this person already names things, and rename in one pass —
without the person having to open forty files to check your work.

Two things make this safe enough to be fast: **nothing is renamed until they've seen the diff**,
and **every batch writes an undo log** so a bad call is one command away from being reversed.
`rename_batch.py` enforces both — it dry-runs unless given `--apply`, and it writes
`.rename_history/` on every apply. Use the script rather than `mv` for exactly this reason.

## Workflow

### 1. Find the files

Get the target folder. If they said "my downloads" or dragged files in, use those paths. If a
folder holds both files to rename and files already named well, only the badly-named ones are in
scope — `inspect_files.py` flags these as `name_is_uninformative: true`, but use judgment: a file
named `notes.docx` isn't junk-patterned yet still says nothing.

### 2. Inspect everything in one shot

```bash
python3 scripts/inspect_files.py "/path/to/folder" --convention "/path/to/folder"
```

This returns, per file, the text worth naming from: document body and headers, Office title
metadata, spreadsheet sheet names and header rows, CSV columns, slide text, image dimensions and
EXIF capture date, plus any dates found in the content. It also reports what the folder's existing
filenames look like. Pass files individually if only some are in scope; `--convention` can point at
a different folder than the files (useful when they're being filed *into* somewhere).

Run this before opening anything by hand. Forty individual file reads is slow and floods context;
the digest is usually all you need.

The output separates facts from advice: everything except `agent_notes` is data about the files,
and `agent_notes` maps whichever findings turned up to what to do about them. Read the notes first
to see what needs attention, then go to the data. `--no-notes` drops the block if you only want
the data. Per-file `warnings` mean a file parsed partially — a malformed sheet or unreadable
metadata — so treat thin content there as missing rather than as evidence the file is empty.

### 3. Open what the digest can't reach

Anything listed under `requires_opening_directly` — images, screenshots, PDFs — needs your eyes.
Read those files directly. This is where screenshots earn their name: a screenshot's entire
identity is what's on screen (the app, the error message, the chart, the conversation), and no
metadata captures it. Read them in parallel batches rather than one at a time.

### 4. Settle the convention

Check `naming_convention` in the digest first. If `is_user_configured` is true, the person has
written down the pattern they want — in `.namerules` or `rename.config.json` in the folder, or in
`~/.config/rename-by-content/config.json`. Follow it exactly. A stated preference outranks anything
you could infer from the filenames, and quietly overriding it is the fastest way to lose their
trust. If they name a different convention in conversation, pass it through instead:

```bash
python3 scripts/inspect_files.py "/path/to/folder" --convention "/path/to/folder" \
  --format 'YYYY-MM-DD_{type}_{subject}'
```

If `naming_convention.error` is set, their config file is broken. Tell them what's wrong in one
line and proceed with the default — don't silently ignore a file they wrote on purpose.

With no configured convention, fall back to what the folder shows. `convention.verdict` tells you
where you stand:

- **`pattern_found`** — match it. Separator, case, date format and position, roughly how many
  fields. Read the `sample_names` yourself too; the statistics describe the shape but the samples
  show the vocabulary (do they write `invoice` or `inv`? `bofa` or `bank-of-america`?).
- **`weak_pattern` or `insufficient_evidence`** — don't reverse-engineer a style out of three
  inconsistent files. Use the default below and *tell them in one line* rather than stopping to ask:

  > No clear naming pattern in this folder, so I used `YYYY-MM-DD_subject.ext`. Tell me a different
  > convention and I'll redo it.

  They can redirect after seeing the preview, which costs them nothing. Blocking on a question
  before they've seen a single result costs them a round trip.

**Default convention:** `YYYY-MM-DD_{type}_{subject}.ext` — ISO date first so the folder sorts
chronologically, lowercase, hyphens inside a field, underscores between fields.

If they tell you a convention they want to keep, offer to save it so they only say it once:

```bash
echo '{"format": "YYYY-MM-DD_{type}_{subject}", "case": "lower"}' > /path/to/folder/.namerules
```

Ask before writing the file — it persists, and that's their call to make.

### 5. Write the names

This is the part that actually matters, and it's judgment, not formatting. The test for a good
name: **someone scanning the folder six months from now can pick the right file without opening
anything.**

- **Lead with what distinguishes this file from its neighbors.** In a folder of forty receipts,
  `receipt` is noise and `home-depot_84-12` is signal. Restating the folder's own name in every
  file wastes the width.
- **Pick the date the file is *about*,** not the day it landed on disk. Priority: a date in the
  content (invoice date, report period, meeting date) > EXIF capture date > filesystem created
  date. Content dates come back in `dates_in_content`.
- **A date you don't have is better omitted than faked.** `acme-invoice-4821.pdf` beats
  `unknown-date_acme-invoice-4821.pdf`. Never write `unknown`, `untitled`, or `misc` into a
  name — those are the names you were hired to remove.
- **Keep proper nouns, drop filler.** `bluecross`, `1420-oak-st`, `q3-board-deck` carry weight;
  `the`, `final`, `v2`, `copy` don't. Aim for roughly 3-8 words.
- **Keep the extension exactly as it was.** Renaming `.jpeg` to `.jpg` breaks links and isn't what
  was asked.
- **Two files can describe the same thing.** If both genuinely are `2026-03-14_acme-invoice`, let
  the script suffix the second one rather than inventing a distinction that isn't in the file.

**Duplicates.** `inspect_files.py` reports `identical_files` — groups that are byte-for-byte the
same, confirmed by hash rather than guessed from matching sizes. Folders accumulate these silently
(`report.pdf` alongside `report(1).pdf`), and surfacing them is often worth more than the renaming
itself. Give the redundant copies the original's name plus `_duplicate` so they sort adjacent and
can be deleted in one selection. Never delete them yourself — that's a different decision from
renaming, and not the one you were asked to make. Sweeping them into a `.duplicates/` subfolder is
a reasonable variation, but ask first: moving files isn't what was approved.

**Near-duplicates** — same text, different size — are a different animal. Don't invent a version
history you can't see: `_v1`/`_v2` asserts an order you'd be guessing at. Label what's observable
instead, like `_full` and `_text-only` for a 712 KB and a 9 KB copy of one memo. A 9 KB `.docx`
can't hold figures, so that distinction is real; which one came first isn't.

Write the plan to a scratch file:

```json
[
  {"path": "/abs/path/IMG_4821.jpg", "new_name": "2026-03-14_receipt_home-depot_84-12.jpg",
   "confidence": "high", "reason": "receipt photo, Home Depot header, total $84.12, dated 3/14/26"},
  {"path": "/abs/path/doc(3).docx", "new_name": "lease_1420-oak-st_signed.docx",
   "confidence": "low", "reason": "lease for 1420 Oak St; no date anywhere in the document"}
]
```

`confidence` is `high` / `medium` / `low` and `reason` is one clause of evidence. Both show up in
the preview so they can zero in on the two files you were unsure about instead of auditing all
forty. Be honest with `low` — a flagged guess that turns out right costs a glance; an unflagged
guess that turns out wrong costs trust in the whole batch.

### 6. Preview, then apply

```bash
python3 scripts/rename_batch.py --plan plan.json
```

Show them the diff. Say in one line what convention you used and which files you flagged. Then wait —
this is the approval step, and it's the reason the rest can move fast.

On their go-ahead:

```bash
python3 scripts/rename_batch.py --plan plan.json --apply
```

Report what changed and mention the undo is there. If they push back on specific names, edit the
plan and re-preview; don't re-inspect the files.

## Undo

```bash
python3 scripts/rename_batch.py --undo --dir "/path/to/folder"          # preview the revert
python3 scripts/rename_batch.py --undo --dir "/path/to/folder" --apply  # revert
python3 scripts/rename_batch.py --history --dir "/path/to/folder"       # list past batches
```

Undo reverts the most recent batch that hasn't already been undone. It refuses to revert a file
that's been edited or moved since — a stale rollback would be worse than none — and says which
files it skipped and why. Repeat to walk further back.

## When things are messier than expected

- **`optional_tools_missing` in the digest** — `pdftotext` or Pillow isn't installed. Nothing is
  broken; those files just need opening one at a time instead of reading in bulk. Mention the
  install command once at the end if it slowed the run down. Don't stop to ask about it.
- **`duplicate_check_incomplete`** — a file couldn't be hashed, so it can't be cleared as unique.
  Don't state that a folder has no duplicates when this is present; say which files went unchecked.
- **`error` on a file** — usually a legacy `.doc`/`.xls` or a corrupt download. Open it with Read
  if it might be text; otherwise leave it out of the plan and mention it. Leaving a file alone is
  a fine outcome and better than naming it from a guess.
- **The digest is empty or boilerplate** (a scanned page with no text layer, a template with only
  headers) — look at the file directly before falling back to a generic name.
- **Sensitive content.** Medical records, pay stubs, legal documents: name them by type and party,
  not by diagnosis, amount, or case detail. Filenames show up in previews, backups, and shared
  screens, and are the least private part of any file.
- **Very large batches (100+).** Inspect all at once — the script is cheap — but preview in the
  same single table. Splitting the approval into chunks turns one decision into ten.

