Skip to content

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.

  1. Plain words, real ideas. Simple language, never dumbed-down concepts. Explain jargon on first use; collect terms in the glossary.
  2. No hype. No marketing adjectives, no "revolutionary," no promises about what something will do for the reader.
  3. Honest about limits. Say what the thing can't do. State uncertainty instead of smoothing it over. Validation notes include what remains unverified.
  4. No invented numbers. Benchmarks, defaults, and specs are either measured or qualified with "depends on your setup." If it wasn't tested, say so.
  5. Checkable. Commands are meant to be run, with "Check:" blocks showing what success looks like. Sources are linked, not asserted.
  6. Respect the reader's setup. Call out Windows/Mac/Linux differences. Don't assume a GPU, a budget, or prior experience.
  7. Safety isn't skipped. Real risks get stated plainly, not buried.
  8. Scoped. Each guide says what it covers and points elsewhere for the rest. No guide pretends to be the whole subject.
  9. 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 --version outputs; 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; n and s sort screens in full; the a toggle in both directions (sparse directory last at 0.0 B, then first at 100.0 MiB; symlinks at 19 B and 6 B); c item counts (.cache 24, Pictures 7, deep 5); g percentage 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; b spawning a shell whose prompt sits in the scanned directory, in both builds; --si units (Videos 35.7 MB); and r rescanning a directory after a 7 MiB file was added from outside — invisible before, listed in size order after.
  • Ch. 3: stat on tiny.txt (size 1, blocks 8 in 512-byte units) and du -B1 (4096); the sparse file by stat (104,857,600 / 0 blocks), by du (0 for its directory) and by the info popup (0.0 B / 100.0 MiB); the hardlink pair by stat (one inode, links=2), by du -B1 on their directory (5,242,880 — shared data once) and on screen (both rows 3.0 MiB with H, total 5.0 MiB); and the reconciliation table — du total 72,208,384, export dsize sum 75,354,112, difference exactly one photograph (3,145,728).
  • Ch. 4: -x both 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: the dsize sum falls by exactly 25,165,824, the three .bin files at depths 1, 1 and 2; --exclude 'chunk-*' on screen — the twenty chunks listed with < at 0.0 B, then hidden by e, then shown again — and the cache's item count still reading 24 with its files excluded; --exclude-caches by export (entry count 59 → 35, the tagged directory present but childless); -L by export in both builds (the file link's asize becomes its target's 25 and its notreg flag clears; the directory link stays notreg and the videos are counted once); the -r refusal 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 ls all 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 flagged H from the stale scan.
  • Ch. 6: exports written by both builds (JSON 2,920 bytes each; cmp shows them differing only in progver and timestamp); the binary export (929 bytes) and the zstd one (758); the JSON read field by field — directories carry asize only, the sparse file carries no dsize key, symlinks carry notreg, the unreadable directory carries read_error, hardlinks carry full dsize twice with hlnkc, nlink and one ino; -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: du only: the recursive -B1 listing 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":true childless 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, du 0, 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-kernfs and the ^ and F flags: 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-delete and --delete-command are listed in 2.9's --help (captured) and named in the chapters at that level. The defaults they modify were verified directly; --no-confirm-delete was 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, and lsof was 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-mtime and 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.