feat(scanner): sparse/clone-aware sizing with hard-link-deduped totals - #13
Merged
Conversation
… key per platform
- Spinner.Stop now waits for the animation goroutine to exit before clearing
the line, so a late frame can no longer reprint over the cleared line and
leave a stale spinner artifact.
- enrichDeviceSupport keys builds by parent dir + "<model> <version>" instead
of the bare name prefix, so version-only folders ("16.4 (build)") can never
collide across DeviceSupport platforms if results are batched together.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YSTf3ozF4SnboZHyEi7Ycc
Replace the per-artifact `du -sk` fork with an in-process walk (Measure) that collects disk usage (allocated blocks, st_blocks×512 — matches du, sparse-accurate) and apparent size (sum of logical file sizes) in one pass. Disk stays primary for sorting/filtering/totals; apparent is new. The table annotates materially sparse artifacts with their apparent size (a Docker.raw image renders "24.0 GB (apparent 460.0 GB)") and JSON carries apparent_size. Hard-linked inodes are counted once per artifact and recorded (dev+ino keyed) so shared blocks can be netted across artifacts in a follow-up. In-process sizing benchmarks at 1.15-1.48x du on real node_modules (~5% of a scan), a negligible cost for the added measurement. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSTf3ozF4SnboZHyEi7Ycc
The grand total — JSON total_size and the table's Total line — now counts blocks shared across artifacts via hard links once, through model.DedupedTotal. A pnpm store blob also hard-linked into a project's node_modules no longer inflates the total; per-artifact sizes stay standalone, only the totals dedup, and the table notes when it did. Keyed by (dev, ino) so identical inode numbers on different volumes (external drives, container filesystems) are never falsely merged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSTf3ozF4SnboZHyEi7Ycc
"apparent" read as jargon and didn't convey that the file only nominally occupies the larger size. The table now renders a sparse artifact as "24.0 GB (appears as 460.0 GB)" — real disk first, the size it presents as second. The JSON field stays apparent_size (the standard du term) for tooling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSTf3ozF4SnboZHyEi7Ycc
Reconcile the CHANGELOG sizing entry — the per-artifact `du` fork is gone, replaced by in-process Measure — and add entries for the apparent_size field, the sparse "(appears as …)" annotation, and the (dev,ino)-deduped total_size. Document the previously-undocumented --json output schema in commands.md. Note accurate sizing in the README feature list. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YSTf3ozF4SnboZHyEi7Ycc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Measure reclaimable space accurately for sparse files and hard-linked stores.
Changes
scanner.Measure) replaces the per-artifactdufork, collecting disk (allocated blocks) and apparent (logical) size in one pass. Disk stays the primary figure for sorting, filtering and totals.Docker.rawimage renders8.6 GB (appears as 494.4 GB)— real disk size, then the size it reports. JSON gains anapparent_sizefield.total_sizeand the table total count blocks shared across artifacts (a pnpm store blob linked intonode_modules) once, keyed by(dev, ino). Per-artifact sizes stay standalone; the table notes when it dedups.Why in-process (reverses an earlier
duchoice)Investigation flipped the premise:
du -skalready reports disk usage, sosizewas already disk-based — what was missing is apparent.ducan give neither apparent size nor cross-artifact inode data, so sizing moved in-process. Benchmarked at 1.15–1.48×duon realnode_modules, and sizing is ~5% of a scan.Not included
Clone-exact dedup (APFS
code_sign_clonecopies): distinct inodes, full blocks each, invisible tostat. Needs extent-level accounting — deferred.Verification
-race+ lint pass; golden output unchanged.Docker.rawsmoke: renders8.6 GB (appears as 494.4 GB), cross-checked againstdu/ls.node_modulessharing a blob → total counts it once.🤖 Generated with Claude Code
https://claude.ai/code/session_01YSTf3ozF4SnboZHyEi7Ycc