Case studies

Photo CLI rename done safely in Rust

A Rust photo renamer with EXIF dates, collision-safe names, sidecars and dry-run by default, built from an archive setup with 23 passing tests.

Time
25 min
Cost
EUR 1.50
Turns
9
Tests
23 of 23 (100%)

Rustclapkamadak-exifwalkdirthiserroranyhowBuilt from the archive setup Rust CLI for photo rename, dedupe, and resumable backup.

What the project is

The setup describes photoflow, a Rust CLI with three subcommands: rename, dedupe and backup. I built the first slice that can be done properly in one sitting: Phase 1 (scaffold and EXIF reading) and Phase 2 (rename) of PLAN.md. The rename command reads EXIF capture time, offset and GPS, builds names like 2023-07-04_090503_paris_001.jpg, and is a dry run unless --apply is passed. Dedupe and backup are stubs that print "not implemented", as Phase 1 asks. The code is in project/.

What works: EXIF reading with None fields for photos without EXIF and an error for corrupt files; an mtime fallback marked source=mtime; a warning when no timezone offset is present; an offline place table; counters per date and place that skip names already taken on disk or in the plan; .xmp and .aae sidecars in both naming patterns; symlinks and read-only files reported and skipped; non-UTF8 names handled with OsString. All 23 tests pass, cargo fmt --check and cargo clippy with warnings denied are clean, and a rename --apply on a temp copy of the fixtures exited 0.

How the setup's files steered the work

CLAUDE.md gave the commands, the approved crate list and the safety rules, so I never had to guess. The rule "dry-run by default, never overwrite" shaped the design: planning is a pure function that only reads the disk, and apply re-checks every target right before each move. REVIEW.md added a useful second pass; I used its list (no unwrap outside tests, PathBuf everywhere, gate moves behind --apply) as a checklist before committing.

The exif-fixtures skill was the most useful file. Its standard-case table gave me the exact fixture set, and its instruction to build the EXIF APP1 segment by hand meant no image crate was needed. The committed fixtures are all under 300 bytes, far under the 50 KB limit, and IMG_0002.xmp is 12 bytes. The permission list and the formatting hook in settings.json only call local tools (cargo, jq, grep), so I kept the file unchanged. I did not run the verify skill as a subagent step; I ran its commands directly.

Where the setup was missing something or wrong

The project name is left as the placeholder "the app" in CLAUDE.md, PLAN.md and the skills, while the Phase 4 test uses env!("CARGO_BIN_EXE_photoflow") and INSTALL.md says cargo init --name the app, which is not a valid crate name. I used photoflow. The setup should pick one name.

The fixture skill says to use tempfile "or a similar test-only helper", but tempfile is not on the approved crate list. I wrote a small TempDir helper with std only.

SPEC.md says to use file mtime in local time, but no approved crate gives a local time zone, so mtime names are in UTC. This is a real gap that the crate list creates. The spec also says offset handling should be used when present but never says what that changes in the name. I kept the wall-clock time and only used the offset to decide whether to warn, and I would add one sentence to the spec.

The spec requires --json on every subcommand, but serde_json is not approved and PLAN.md only mentions asking for it in Phase 4. I did not add it, so --json is not built. Phase 3 asks for hashing on spawn_blocking, and I stopped before dedupe because of scope and time rather than ship a synchronous version that REVIEW.md would reject.

The Phase 1 goal prompt wants rename --dry-run, but the spec says dry-run is the default and passing both flags is an error. I accepted --dry-run as a no-op flag that conflicts with --apply.

The kill-and-resume backup test against a hand-written mock server in Phase 4 is by far the riskiest piece. I would split Phase 4 in two: the state file and chunk logic first, the S3 client second.

What I would change in the setup

Fix the name once and use it everywhere. Add serde_json to the approved list, or move the first use earlier in the plan. State the timezone rule in one sentence. Add a bundled place table size and license note to the spec, since "offline GPS lookup" otherwise invites inventing a dataset. Merge the Phase 1 and Phase 2 goal prompts, because the Phase 1 prompt ends with a rename command that Phase 2 immediately rewrites. Finally, tell the reader that the formatting hook only fires on Edit and Write tool calls, so shell-generated files need cargo fmt by hand.

What is left

Dedupe, backup, --json, progress bars and a real place dataset are not started. The open questions in SPEC.md are unchanged.

The full prompts and every file are included in Pro

  • Full prompts
  • Files
See ProSign in