What next holds beyond main, the fixes from your trial run (#56):
scan, report and trees print a first line on stderr naming the database file and where its path came from, and README "Database" says when scan writes each file (#111).
scan shows each phase as [n/5] with its progress, elapsed time and ETA, from the moment the phase starts and while it waits; under cron a line comes at least every 5 seconds, and on a terminal no line is wider than the terminal (#112).
trees (#113) and report (#114) show their phases the same way until their summary lines, and their rows never land on the progress line on a shared terminal. report no longer waits on SQLite sorting every duplicate before its first row; its output is unchanged.
New sfdupes info: the database's path, file sizes, last change, schema version and record counts (#117).
The rate covers the last 5 seconds instead of the phase's average, and a phase with nothing finished for 10 seconds says stalled and where it waits (#125).
scan never records anything on tmpfs, proc, sysfs or other filesystems that are not persistent, and skips a per-system default path list (/proc, /sys, /dev, /run, /tmp, /var/tmp on Linux) unless given --no-default-excludes (#124). It fails if it cannot read the mount list.
scan never records its own database files when the database lies inside a scanned path (#115), and creates a missing database directory where a path with .. after a symlink leads (#119).
README "Install" leads with @latest; go.mod retracts v0.0.1; --version of a go-installed binary prints its module version (#116).
The canonical build files re-vendored from sneak/prompts. Check builds now also keep Go's downloaded modules, golangci-lint's cache and the system-package layer, on your ruling in sneak/prompts#130; every lint and test step still runs on every build (#109).
Whatever is on next when you merge ships. After you merge, main's new tip is tagged v0.0.3 once it passes script/cibuild; that release carries the retraction of v0.0.1.
Model: opus-5-5
What `next` holds beyond `main`, the fixes from your trial run (https://git.eeqj.de/sneak/sfdupes/issues/56):
- `scan`, `report` and `trees` print a first line on stderr naming the database file and where its path came from, and README "Database" says when `scan` writes each file (https://git.eeqj.de/sneak/sfdupes/issues/111).
- `scan` shows each phase as `[n/5]` with its progress, elapsed time and ETA, from the moment the phase starts and while it waits; under cron a line comes at least every 5 seconds, and on a terminal no line is wider than the terminal (https://git.eeqj.de/sneak/sfdupes/issues/112).
- `trees` (https://git.eeqj.de/sneak/sfdupes/issues/113) and `report` (https://git.eeqj.de/sneak/sfdupes/issues/114) show their phases the same way until their summary lines, and their rows never land on the progress line on a shared terminal. `report` no longer waits on SQLite sorting every duplicate before its first row; its output is unchanged.
- New `sfdupes info`: the database's path, file sizes, last change, schema version and record counts (https://git.eeqj.de/sneak/sfdupes/issues/117).
- The rate covers the last 5 seconds instead of the phase's average, and a phase with nothing finished for 10 seconds says `stalled` and where it waits (https://git.eeqj.de/sneak/sfdupes/issues/125).
- `scan` never records anything on tmpfs, proc, sysfs or other filesystems that are not persistent, and skips a per-system default path list (`/proc`, `/sys`, `/dev`, `/run`, `/tmp`, `/var/tmp` on Linux) unless given `--no-default-excludes` (https://git.eeqj.de/sneak/sfdupes/issues/124). It fails if it cannot read the mount list.
- `scan` never records its own database files when the database lies inside a scanned path (https://git.eeqj.de/sneak/sfdupes/issues/115), and creates a missing database directory where a path with `..` after a symlink leads (https://git.eeqj.de/sneak/sfdupes/issues/119).
- README "Install" leads with `@latest`; `go.mod` retracts `v0.0.1`; `--version` of a go-installed binary prints its module version (https://git.eeqj.de/sneak/sfdupes/issues/116).
- The canonical build files re-vendored from `sneak/prompts`. Check builds now also keep Go's downloaded modules, golangci-lint's cache and the system-package layer, on your ruling in https://git.eeqj.de/sneak/prompts/issues/130; every lint and test step still runs on every build (https://git.eeqj.de/sneak/sfdupes/issues/109).
Whatever is on `next` when you merge ships. After you merge, `main`'s new tip is tagged `v0.0.3` once it passes `script/cibuild`; that release carries the retraction of `v0.0.1`.
Model: opus-5-5
Every vendored file, REPO_POLICIES.md and every model script is the
copy at sneak/prompts e3f79c4, with this repository's own entries kept
after the canonical content. Lint and test are Dockerfile phases that
write no image; make test runs the suite under the race detector as
nobody. Every build in script/ passes a new CACHEBUST value, which every
stage declares first, so every RUN step runs again; the steps that
compile keep Go's build cache, and both go test runs pass -count=1.
Dockerfile.lint, script/verify-lint-image-pin and make test-race are
gone, prettier runs on the host, and .claude/settings.json is deleted.
Deviation: the set comes from e3f79c4 on next rather than dd4027b.
Judgement call: the test phase mounts the cache at nobody's
/tmp/.cache/go-build, owned by nobody.
Model: opus-5-5
sneak
was assigned by clawbot2026-10-16 04:07:17 +02:00
scan, report and trees now print one line on stderr before anything
else, naming the database and whether its path is the default or comes
from SFDUPES_DATABASE; scan also says when the file is new. report and
trees print it before opening the database, so a missing-database error
follows it. A relative path gets the working directory put in front of
it, with its symlinks resolved as the kernel reports it, and is not
cleaned: a ".." after a symlink names another file than the cleaned
path does. README "Database" lists when scan writes each file.
Unverified: the line on a terminal has no test of its own.
Model: opus-5-5
Every scan display names its phase and its place among the five, as
"[2/5] walk", with its progress, elapsed time and ETA. load counts the
records first; the walk estimates its total from the records under the
operands; the content phase adds up the sizes of the matching groups
and says it is searching until the first record returns. Each display
redraws every 100 ms on a terminal and every 5 seconds in plain mode.
On a terminal no line reaches the last column: a line too wide drops
its rate, then its bar. After the terminal is made narrower than the
widest line drawn, the display makes a new library object, whose erase
fits.
Judgement call: load and the content checks draw the bar too, once their totals are known.
Judgement call: the bar is 10 columns wide, not the width the line leaves.
Model: opus-5-5
trees now runs in four phases, open, read, group and write, each under
the progress display scan uses, until the summary line. open covers a
wait on a busy database. read counts the records and reads them in one
read-only transaction, so the two agree during a scan; group counts the
directories, and write the groups. The rows reach stdout through a
writer. When stdout and stderr are the same file, it clears the display
and holds its lock while writing, so no row shares a line with the
display on a shared terminal; otherwise it writes without the lock, so
a slow reader of stdout cannot stop the display. Each rate names what
its phase counts.
Judgement call: opening the database is a phase of its own, so there are four phases, not the three the issue sketches.
Model: opus-5-5
report now runs in two phases, open and read, under the progress
display scan and trees use, until the summary line. open covers a wait
on a busy database. read counts the records, then reads them in the
same read-only transaction in signature order, the files_signature
index read backwards, so SQLite sorts nothing and the read ends at
exactly its count. The duplicate groups are formed in Go from runs of
equal signatures; only those of the size being read are held, and they
are written, ordered by first path, before the next size. The rows go
through the display's stdout writer. stdout and the summary line are
unchanged.
Judgement call: writing is part of read, not a phase of its own, since each size's rows are written while the read goes on.
Model: opus-5-5
info prints the database's path and source, the sizes of the database
file and its -wal file, when the database file last changed, the schema
version and the record counts, one per line as a name, a tab and the
value. It opens the database as report does and shows an open and a
count phase on stderr. The schema version and the counts come from one
read-only transaction, the counts from one query.
seedDatabase now closes test databases out of WAL mode, as scan does;
otherwise info's own connection creates an empty -wal file. The open
phase's name and text became constants for the linter, and the scan
command moved to newScanCommand to keep newRootCommand short enough.
Judgement call: the lines are written after the last display ends, so they skip the display's stdout writer.
Model: opus-5-5
The rate was the phase's average, so a burst after a long wait read as
over 1,000 files a second. It now covers the last 5 seconds, or the
time since the previous line off a terminal; the ETA still comes from
the average. Once no item has completed for 10 seconds, the rate gives
way to "stalled" and where the phase waits: disk, db, out (stdout) or
cpu, or two of them, as "stalled disk/db". A line too wide for the
terminal loses its bar first, then its rate or those words, so the
phase, progress, elapsed time and ETA stay whole. The content phase's
last partial batch is now written under a display of its own.
Deviation: the stall words hold no time and name where the phase waits, not what it does.
Judgement call: trees' group phase, which works in memory, says "cpu".
Model: opus-5-5
scan never records or reads anything on a filesystem that is not
persistent (tmpfs, proc, sysfs and the like), however it reaches it; no
flag turns this off. It notes each such filesystem's device ID as it
starts: on Linux from /proc/self/mountinfo, on macOS by lstat of each
listed mount point. Below an operand the walk also skips the default
excludes, a path list per system (Linux, macOS, and Windows, not used
yet); --no-default-excludes turns off only those. A default exclude that
is itself an operand gets no skip line. The tests stop at once, saying
why, when TMPDIR is not on a persistent filesystem.
Judgement call: an unreadable mount list fails the scan, and no flag gets round it.
Judgement call: a file on such a filesystem below an operand gets the same line as a directory.
Judgement call: such an operand that another operand's walk also reaches is named twice.
Unverified: whether macOS's mount list gives the device lstat reports, so macOS keeps lstat, and a hidden mount there can still add another filesystem's device.
Unverified: the macOS code is never built or run here; bind mounts need root to test.
Model: opus-5-5
When the database lay under a scanned operand, the walk recorded the
database file, its -wal and -shm files and its .lock file like any other
file, so every rescan updated them and report paired them with empty
files. Once the database and the lock file are open, the scan now notes
their device and inode, and the walk passes over a file with one of
them, as an operand or below one, printing no line and counting
nothing. Matching by device and inode holds through a symlinked
directory, where comparing paths would not. A record an earlier scan
left for one of them is deleted as unverified. README's smoke-test note
on keeping the database out of the tree is gone, as it no longer holds.
Judgement call: walkRules, in exclude.go, carries the noted files to the walk.
Model: opus-5-5
scan created a missing database directory from filepath.Dir of the
path, which cleans it as text and drops each ".." together with the
name before it. After a symlinked directory that is another directory
than the one the system reaches when it opens the file, so scan left an
empty directory beside the symlink and then failed to create the
database. lockScanDatabase and openScanDatabase now both call
createDatabaseDirectory, beside databasePath, which cuts the last
element off the path as text and creates what is missing along it. A
comment in databasePath now says why the working directory has its
symlinks resolved.
Unverified: a missing directory before a ".." in the path is created along the way; no test covers it.
Model: opus-5-5
README "Install" now leads with go install ...@latest: until 1.0 each
release is a v0.0.N tag on main's tip, so @latest gives that tip once
it has passed the build. It also says what @main and v0.0.1 give and
how to check an installed binary. go.mod retracts v0.0.1, the early
build without a database.
sfdupes --version printed "dev" for every go-installed binary, since
only the linker set Version. When the linker sets none, it now prints
the main module's version from runtime/debug.ReadBuildInfo; "dev" is
left only when neither is known. Version is now empty by default.
The retraction takes effect only once a release carrying it is the
newest.
Model: opus-5-5
Re-vendors REPO_POLICIES.md and script/cibuild, docker, lint and test
from sneak/prompts at 0653579, on sneak's ruling in
sneak/prompts#130. In the Dockerfile's lint,
test and builder stages, the system-package install and the module
download now come before ARG CACHEBUST, so Docker keeps those layers
until the base image, the install line, go.mod or go.sum changes.
golangci-lint run also mounts golangci-lint's cache. Every step that
lints, tests or compiles still runs on every check build, and go test
keeps -count=1 and -race. README and TODO.md say the same.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
nextholds beyondmain, the fixes from your trial run (#56):scan,reportandtreesprint a first line on stderr naming the database file and where its path came from, and README "Database" says whenscanwrites each file (#111).scanshows each phase as[n/5]with its progress, elapsed time and ETA, from the moment the phase starts and while it waits; under cron a line comes at least every 5 seconds, and on a terminal no line is wider than the terminal (#112).trees(#113) andreport(#114) show their phases the same way until their summary lines, and their rows never land on the progress line on a shared terminal.reportno longer waits on SQLite sorting every duplicate before its first row; its output is unchanged.sfdupes info: the database's path, file sizes, last change, schema version and record counts (#117).stalledand where it waits (#125).scannever records anything on tmpfs, proc, sysfs or other filesystems that are not persistent, and skips a per-system default path list (/proc,/sys,/dev,/run,/tmp,/var/tmpon Linux) unless given--no-default-excludes(#124). It fails if it cannot read the mount list.scannever records its own database files when the database lies inside a scanned path (#115), and creates a missing database directory where a path with..after a symlink leads (#119).@latest;go.modretractsv0.0.1;--versionof a go-installed binary prints its module version (#116).sneak/prompts. Check builds now also keep Go's downloaded modules, golangci-lint's cache and the system-package layer, on your ruling in sneak/prompts#130; every lint and test step still runs on every build (#109).Whatever is on
nextwhen you merge ships. After you merge,main's new tip is taggedv0.0.3once it passesscript/cibuild; that release carries the retraction ofv0.0.1.Model: opus-5-5