DEV Community

szp2005
szp2005

Posted on

Twelve of thirteen stale docs were kept alive only by each other

My coding agents are prolific note-takers. Every non-trivial session leaves a PLAN.md, a SUMMARY.md, a HANDOFF.md, sometimes a FINAL_REPORT_V2.md. Across 36 repos on this machine there are about 1,200 markdown files, and I could not tell you which ones anybody still opens.

The obvious cleanup is a glob and a date filter:

find . -name 'PLAN*.md' -o -name 'SUMMARY*.md' -mtime +30 -delete
Enter fullscreen mode Exit fullscreen mode

I did not run that, because I already knew what it would hit. A PLAN.md from May can still be the file a README points at when it explains why the approach changed. Age alone says nothing about whether a file is load-bearing.

The rule that looked right

So the next rule: keep anything that something else links to. Count inbound references, and if the count is above zero, the file is still wired into the repo. Old and unreferenced means safe to move.

That rule survived about a day. Here is the scan that killed it, from a trading repo with a reports/ folder:

reports/E_F_G_RESULTS.md              116d  stale  refs=1
reports/FINAL_HONEST_PLAN.md          116d  stale  refs=1
reports/FUNDING_HARVEST_REPORT.md     116d  stale  refs=2
reports/HONEST_FINDINGS.md            116d  stale  refs=1
reports/J_SUMMARY.md                  114d  stale  refs=1
reports/FINAL_REPORT.md               114d  stale  refs=6
... 13 files, all 114-116 days old, every one with refs > 0
Enter fullscreen mode Exit fullscreen mode

Every file passed the "someone links to it" test. Then I traced where the links came from:

grep -rl "J_SUMMARY\.md" --exclude-dir=.git .
# reports/L1_AUDIT_REPORT.md
Enter fullscreen mode Exit fullscreen mode

L1_AUDIT_REPORT.md is in the same folder and is just as dead. I walked all thirteen. Twelve of them had every single inbound link coming from another file inside reports/. The folder was citing itself in a circle. Only FINAL_REPORT.md had a reference from outside: mql5/README.md, and a Python script that actually runs.

A reference count of 1 was not evidence that anyone used the file. It was evidence that the agent wrote two files in the same session and made one mention the other.

Three grades, and only one of them moves

I stopped trying to find a rule that decides. The scanner now sorts into three buckets and hands the ambiguous one back to me:

h.grade = h.ageDays <= days   ? 'active'   // touched recently, leave it
        : h.refs > 0          ? 'stale'    // old but linked, I read these by hand
        :                       'orphan';  // old and unlinked, movable
Enter fullscreen mode Exit fullscreen mode

Across those 36 repos: 687 active, 172 stale, 176 orphan. The 172 in the middle are exactly the files the glob would have eaten, and the ones the reference rule would have blessed. They are not a category the tool can resolve, so it doesn't pretend to.

Orphans get moved rather than deleted. fs.renameSync puts them in .mdsweep/trash/<timestamp>/ next to a manifest that records every original path. mdsweep undo renames them back. Nothing in your tree gets unlinked; the only rmSync in the codebase targets the trash directory itself after a full restore.

What I would not put on a slide

The same scan flags 1,035 of 1,199 files, which is 86%, and that number is almost meaningless. One of the three detection signals is "untracked by git," so a repo with a messy working tree lights up wholesale. The flag rate measures my hygiene, not the tool's precision. The grading is the part that earns its keep.

The stale bucket is also a genuine dead end, not a staging area. The reports folder above proves the tool cannot distinguish a live citation cluster from a dead one, and I do not think heuristics will get there. Thirteen files is a two-minute read for a human. Thirteen hundred would not be, and I have no answer for that yet.

It is a single .mjs file, no dependencies, Node 18+. It reads your repo and prints a table; you have to pass --apply before it touches anything.

git clone https://github.com/szp2005/mdsweep
node mdsweep/bin/mdsweep.mjs scan ~/code/your-repo
Enter fullscreen mode Exit fullscreen mode

I built it (szp2005/mdsweep) for my own repos, and the reports folder above is why the middle bucket exists at all. If your agents also write more markdown than you read, the scan is read-only, so the worst case is that you find out your repos were fine.

Top comments (1)

Collapse
 
raknaos profile image
Baptiste Le Bouquin

Age-based cleanup is the trap I fell into too. I run a small fleet of coding agents on a VPS and every session spawns PLAN.md / SUMMARY.md files; the "delete after 30 days" glob looked obvious until it nuked two docs that were still the only explanation of a rewrite.

What finally worked for me: reachability instead of age. A file "lives" if some README, source comment, or another kept doc references it — so the mutual-citation graph you describe is exactly what bit me too. Did you consider a grep-based pass from entry points, or was the transitive-closure cost what stopped you?