You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
v0.2.5: Fix organize silently overwriting files with duplicate names
execute_plan() computed its destination as `cat_dir / new_name` with no
existence check and copied with shutil.copy2(), which overwrites. When the
AI plan assigned the same category + new_name to two different files, only
one survived on disk -- while stats["copied"] counted both and _summary.md
listed both as organized successfully. Silent data loss with a success
report.
Dedup masks this in practice: compare --link converts identical content to
symlinks before organize runs, so byte-identical files rarely collide. The
exposure is genuinely different content earning the same name -- two
unrelated shopping lists both becoming shopping-list.txt. The defect has
been present since organize was introduced.
Found while designing batched AI backends, where it becomes much more
likely: a backend processing files in batches has no view of the names
earlier batches already assigned.
Collision handling:
- _resolve_unique_dest() claims a destination no other entry in the same
run is using, appending -2, -3, ... before the extension so file type is
preserved (script.py -> script-2.py)
- Keys on the lowercased path, so Notes.txt and notes.txt are treated as a
collision on Windows rather than one silently winning
- Tracking is scoped to a single run, never checked against files already
on disk. Checking the filesystem would break idempotency: re-running
organize would produce -2, -3 copies on every pass instead of
overwriting its own previous output
- Every rename is reported in three places -- the summary counts, a new
"Renamed to avoid collisions" section in _summary.md, and the per-file
mapping. A silent rename is nearly as bad as a silent overwrite, because
the summary would stop describing the disk
- stats["renamed"] added and surfaced in the CLI output
Policy is auto-rename and report, not abort. One name clash must not
discard an otherwise complete run; the invariant that matters is that no
file is lost, not that no name repeats.
Plan validation:
- validate_plan(plan, base_dir, expected_sources=None) checks a plan
before anything touches the filesystem: every source must exist, no
source may appear twice, every expected source must be present, and
duplicate destinations are reported
- organize now refuses to run when a plan would lose or mis-file content.
Duplicate destinations are informational (execute_plan handles them);
missing, duplicated, and nonexistent sources are fatal
Prompt:
- prompts/organize.md had no uniqueness requirement at all. "Every file
MUST get its own new_name" meant "not null", as the following clause
says. Added an explicit rule that every category + new_name pair must be
unique, and that similar files be distinguished by content rather than
by numeric suffix
New tests (50 total, up from 36) -- first automated suite in tests/, which
previously held only one-offs/ proof-of-concept scripts:
- test_colliding_names_do_not_lose_content: reproduces the defect; two
entries sharing a destination must yield two files with both contents
- test_collision_rename_is_reported: renames appear in the summary
- test_collision_across_categories_is_not_a_collision: the same filename
in different categories is legitimate and must not be renamed
- test_extension_preserved_when_de_colliding: script.py -> script-2.py
- test_case_only_difference_collides_on_windows: Notes.txt vs notes.txt
- test_rerun_does_not_accumulate_duplicates: idempotency guard
- test_many_collisions_all_survive: a 12-way collision keeps all 12 files
- test_rename_counter_is_reported_in_stats
- test_validate_plan_*: duplicate, missing, and nonexistent sources
- no-regression guards on the ordinary non-colliding path
Also adds tests/checklists/ with a human test checklist covering what
mocks cannot: whether _summary.md reads clearly, real Claude CLI output,
Windows case-insensitivity, whether the refusal message is actionable, and
re-run idempotency against a real organized folder.
Design: 2026-07-25__14-07-53__editor-mode-architecture-and-sublime-archaeology.md
Design: 2026-07-26__03-15-17__addendum__local-model-backend-and-collision-defect.md
Copy file name to clipboardExpand all lines: notepad_cleanup/prompts/organize.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -39,6 +39,8 @@ Return ONLY a JSON array. No markdown fencing, no explanation, just raw JSON:
39
39
## Rules for naming and categorizing
40
40
41
41
- Every file MUST get its own `new_name` — never set it to null
42
+
-**Every `category` + `new_name` pair MUST be unique across the whole plan.** Two files may share a category, and the same filename may appear in *different* categories, but no two entries may target the same `category/new_name`. If two files are genuinely similar, distinguish them by content (`grocery-list-june.txt` and `grocery-list-hardware-store.txt`), not by a numeric suffix
43
+
- If you cannot tell two files apart well enough to name them distinctly, still give them different names — a duplicated name means one file would overwrite the other
42
44
- Each tab is preserved as an individual file, even short notes
43
45
- Use lowercase-with-dashes for folder and file names
0 commit comments