Writing Criteria
Every guide in this repo is written to the same standard. If you're adding or editing a guide — human or AI — hold it to these.
- Plain words, real ideas. Simple language, never dumbed-down concepts. Explain jargon on first use; collect terms in the glossary.
- No hype. No marketing adjectives, no "revolutionary," no promises about what something will do for the reader.
- Honest about limits. Say what the thing can't do. State uncertainty instead of smoothing it over. Validation notes include what remains unverified.
- No invented numbers. Benchmarks, defaults, and specs are either measured or qualified with "depends on your setup." If it wasn't tested, say so.
- Checkable. Commands are meant to be run, with "Check:" blocks showing what success looks like. Sources are linked, not asserted.
- Respect the reader's setup. Call out Windows/Mac/Linux differences. Don't assume a GPU, a budget, or prior experience.
- Safety isn't skipped. Real risks get stated plainly, not buried.
- Scoped. Each guide says what it covers and points elsewhere for the rest. No guide pretends to be the whole subject.
- Structure over prose. Tables, before/after examples, symptom → fix. Scannable beats essay.
Verification notes for this guide
Standard 3 and 4 applied to this guide,
specifically. Two builds of the one tool
were run against the lab: ncdu 1.19
(the Ubuntu 24.04 package, extracted and
run from a scratch directory) as the
primary, and ncdu 2.9 (the upstream
static binary from dev.yorhel.nl) as the
cross-check, on Ubuntu 24.04. Every
interactive claim was exercised in a real
terminal (tmux panes, keys sent one by
one, screens captured); non-interactive
claims were exercised through exports
parsed field by field. Working transcripts
— 53 files — are kept with the build
(ncdu-lab/transcripts/).
The lab. One tree, built by
examples/build-tree.sh, identical on
every rebuild: 59 entries including the
top directory (39 files, 18 directories,
2 symlinks). Ground truth measured with
stat and du before any ncdu claim was
written: total disk usage 72,208,384
bytes, total apparent size 177,034,515
bytes, the whole gap being the one sparse
file. The specimens are named in
examples/lab-notes.txt: binary files of
exact sizes (1 byte to 100 MiB apparent),
a hardlink pair sharing one inode, a
tagged cache with twenty 4096-byte files,
a space-bearing filename, a five-deep
directory chain, an empty directory, and
two symlinks. Three further fixtures were
built beside it: homedir-del/ (chapter
5's deletion copy), a two-filesystem
fixture with a tmpfs mounted over one
directory (chapter 4's -x), and a
two-directory permission fixture in /tmp
scanned as user nobody (chapter 9).
Verified by running, per chapter:
- Ch. 1: both
--versionoutputs; the first screen from both builds (title, rows and footer totals identical: 68.9 MiB disk, 168.8 MiB apparent, 58 items); the tree's entry counts by kind. - Ch. 2: movement by arrows (descend,
ascend, descend again); the cursor's
landing on the
/..row on entering a directory, proven with an attribute capture showing which row is highlighted;nandssort screens in full; theatoggle in both directions (sparse directory last at 0.0 B, then first at 100.0 MiB; symlinks at 19 B and 6 B);citem counts (.cache24, Pictures 7, deep 5);gpercentage view (Videos 49.4%, Downloads 36.3%, Pictures 8.3%, cache 5.9%); the help screen's Keys tab across all three of its pages and the Format tab across both (the flag table in the chapter is that text); the info popup without-e(Type line) and with-e(Mode, UID, GID, mtime) in 1.19, and in 2.9 (which adds Link count and Inode lines); the Links tab listing the hardlink partner;bspawning a shell whose prompt sits in the scanned directory, in both builds;--siunits (Videos 35.7 MB); andrrescanning a directory after a 7 MiB file was added from outside — invisible before, listed in size order after. - Ch. 3:
staton tiny.txt (size 1, blocks 8 in 512-byte units) anddu -B1(4096); the sparse file bystat(104,857,600 / 0 blocks), bydu(0 for its directory) and by the info popup (0.0 B / 100.0 MiB); the hardlink pair bystat(one inode, links=2), bydu -B1on their directory (5,242,880 — shared data once) and on screen (both rows 3.0 MiB withH, total 5.0 MiB); and the reconciliation table —dutotal 72,208,384, exportdsizesum 75,354,112, difference exactly one photograph (3,145,728). - Ch. 4:
-xboth ways over the tmpfs fixture — by export (both files without-x; only the same-device file with it) and on screen (the mounted directory present with the>flag at 0.0 B);--exclude '*.bin'by export: thedsizesum falls by exactly 25,165,824, the three.binfiles at depths 1, 1 and 2;--exclude 'chunk-*'on screen — the twenty chunks listed with<at 0.0 B, then hidden bye, then shown again — and the cache's item count still reading 24 with its files excluded;--exclude-cachesby export (entry count 59 → 35, the tagged directory present but childless);-Lby export in both builds (the file link'sasizebecomes its target's 25 and itsnotregflag clears; the directory link staysnotregand the videos are counted once); the-rrefusal message;-e's popups (Ch. 2). - Ch. 5: the whole delete path on the
copy only — the file dialog verbatim;
its default focus proven by an Enter
that deleted nothing; confirmation by
← + Enter deleting forgotten.zip, with
the listing, the footer (Downloads
25.0 → 20.0 MiB, items 4 → 3) and an
outside
lsall agreeing; the directory dialog's "and all of its contents?" wording, captured and cancelled (the recursive confirm itself was not executed for the camera; it is the same dialog flow as the file's, and the chapter says what it does without a transcript of the aftermath); and the hardlink deletion — one name of the pair deleted in the copy, the parent total unchanged at 5.0 MiB, the survivor still flaggedHfrom the stale scan. - Ch. 6: exports written by both builds
(JSON 2,920 bytes each;
cmpshows them differing only inprogverandtimestamp); the binary export (929 bytes) and the zstd one (758); the JSON read field by field — directories carryasizeonly, the sparse file carries nodsizekey, symlinks carrynotreg, the unreadable directory carriesread_error, hardlinks carry fulldsizetwice withhlnkc,nlinkand oneino;-o -to stdout (1.19) and-f -from stdin (2.9); browsing an import with the lab renamed away; and the full import matrix — JSON both directions (title shows[imported]), binary and zstd read by 2.9 ([from file]/[imported]), both refused by 1.19 with the "Zero-byte found in JSON stream" error, captured. Deleting inside an import: the "Deletion feature disabled." message, captured. - Ch. 7: the recipes compose only operations verified in chapters 2–6 and 9; no new primitives are introduced.
- Ch. 8:
duonly: the recursive-B1listing and per-directory totals quoted are captures, and its agreement with ncdu was checked number by number in chapter 3. - Ch. 9: the permission specimen —
scanned as
nobody, the locked directory shows!at 0.0 B on screen and"read_error":truechildless in the export, the scan exiting 0 with the other 100,000 bytes counted and the locked 200,000 nowhere in any total; the missing-path error verbatim; the no-TTY refusal verbatim with exit 1; the deleted-but-open file proven via /proc (directory empty,du0, the open handle's fstat still showing 40,960 blocks); and box borders rendering as letters in a non-UTF-8 tmux session, observed while capturing.
Capture hygiene (declared trims):
quoted screens are trimmed of their
trailing blank rows, and the path in
quoted headers is shortened to
/home/... — the full paths stand in the
transcripts. Two quoted popups keep
1.19's dot thousands separators
(3.145.728) exactly as captured, with
the difference from 2.9's commas noted in
the text. Nothing was cut silently: every
screen in the chapters exists whole in
ncdu-lab/transcripts/.
Build-machine weather, stated once:
the lab's home filesystem is an overlay
on which directories occupy no data
blocks of their own and short symlinks
occupy a full 4096-byte block; on an ext4
home both numbers differ (directories
usually a block, short symlinks usually
none) and every relative claim in the
chapters survives, because each is stated
as a comparison, not an absolute. New
files arrive group nogroup under the
workspace umask, which is why popups show
UID 0 / GID 65534. Inode numbers in the
captures are that build's; a rebuild
renumbers them, as lab-notes.txt says.
Documented-only (not run), and why:
- The neighbours beyond
du— dust, dua-cli, gdu, Baobab, Filelight, QDirStat, GrandPerspective, DaisyDisk, WinDirStat, WizTree: placed from their documentation and ncdu's own similar-projects list. None is installed on the build machine, and no output from any of them is quoted. WizTree's MFT-reading speed claim is its documented design, repeated as such. -X/--exclude-from: named from the help text; the exercised exclusion is the single-pattern form, whose matching the file form shares.--exclude-kernfsand the^andFflags: no pseudo-filesystem or firmlink exists in the lab; the flag texts are quoted from the built-in help's Format tab, which was captured.- 2.x feature toggles:
--enable-delete,--enable-shell,--enable-refresh,-rr,--no-confirm-deleteand--delete-commandare listed in 2.9's--help(captured) and named in the chapters at that level. The defaults they modify were verified directly;--no-confirm-deletewas deliberately not exercised — a guide that practises deletion safety does not need a transcript of the safeguard removed. - Refresh (
r) inside an imported view: imports were browsed and delete-tested, not refreshed; rescanning an import would measure the local paths, and the chapters claim nothing about it. lsof +L1: named in chapter 9 as the tool for deleted-but-open files; the mechanism itself is proven via /proc, andlsofwas not part of the build.- The ext root reserve (5%): stated from filesystem documentation as a suspect to check; the lab's overlay has no such reserve to measure.
- Sparse-preserving copies
(
cp --sparse=auto,rsync --sparse): named in chapter 3 as the contrast to a plain copy; no copy was performed. - 2.x extras: parallel scanning (
-t), the config file, and the startup display options (--sort,--enable-natsort,--group-directories-first,--hide-hidden,--show-mtimeand kin) are from 2.9's captured--help; the interactive equivalents that were exercised are the keys of chapter 2. - macOS and Windows behaviour: no such system was available to the build; platform statements come from the upstream site (packaging, the third-party Windows binary) and the tools' own pages.
- Installation on a reader's machine is described, not rehearsed: the two builds here were an extracted distro package and a static tarball, run from a scratch directory.
Specimen discipline, for the record: every
name in the tree is invented — a generic
home directory of documents, downloads,
pictures and videos — every size is
deliberate, and the only deletions in the
guide happen inside homedir-del/, a
purpose-built copy, plus one 20 MiB
throwaway file created to be unlinked
under an open handle in chapter 9. No
command in the guide was aimed at a real
system directory except the read-only
examples stated as audits, which are shown
as commands to adapt, not transcripts.