diff --git a/dotfiles/agents/skills/disk-space-assessment/references/direnv-gc-roots.md b/dotfiles/agents/skills/disk-space-assessment/references/direnv-gc-roots.md index a759f737..121fef71 100644 --- a/dotfiles/agents/skills/disk-space-assessment/references/direnv-gc-roots.md +++ b/dotfiles/agents/skills/disk-space-assessment/references/direnv-gc-roots.md @@ -20,8 +20,9 @@ The script writes a timestamped JSON artifact under `~/.cache/ncdu/` and updates - **Collectively direnv-only**: direnv closure paths absent from the union of every observed non-direnv root closure. Removing all direnv roots could make these collectible. - **Outside non-direnv roots**: one project's closure after subtracting non-direnv-retained paths. This remains an upper bound because other direnv projects may share it. - **Marginal unique**: paths retained by exactly one direnv project and no non-direnv root. This is the best logical NAR-size estimate of what removing only that project's `.direnv` could make collectible. +- **Shared direnv-only**: paths absent from non-direnv roots but retained by two or more direnv projects. Inspect the recorded retaining-project set to identify cleanup campaigns that must move together. -Rank projects by marginal unique size, then inspect age, worktree state, active shells/builds, and project importance. Large closure size with near-zero marginal unique size indicates heavy sharing and little immediate benefit from removing that project alone. +Rank projects by marginal unique size, then inspect age, worktree state, active shells/builds, and project importance. Large closure size with near-zero marginal unique size indicates heavy sharing and little immediate benefit from removing that project alone. Use `top_shared_direnv_only_paths` in the JSON artifact to explain the gap between collectively direnv-only size and the sum of marginal project sizes. ## Validate Candidates diff --git a/dotfiles/agents/skills/disk-space-assessment/scripts/direnv_gc_roots_audit.py b/dotfiles/agents/skills/disk-space-assessment/scripts/direnv_gc_roots_audit.py index 0ee488d2..1e7bb054 100644 --- a/dotfiles/agents/skills/disk-space-assessment/scripts/direnv_gc_roots_audit.py +++ b/dotfiles/agents/skills/disk-space-assessment/scripts/direnv_gc_roots_audit.py @@ -141,12 +141,27 @@ def main() -> int: "closure_nar_bytes": total(project_paths), "outside_non_direnv_nar_bytes": total(outside_non_direnv), "marginal_unique_nar_bytes": total(marginal), + "shared_with_other_direnv_nar_bytes": total(outside_non_direnv - marginal), "roots": [{"source": source, "target": target} for source, target in entries], "top_marginal_paths": top_paths(marginal, path_sizes, args.top), } ) projects.sort(key=lambda item: (item["marginal_unique_nar_bytes"], item["closure_nar_bytes"]), reverse=True) + shared_direnv_only = {path for path in direnv_only if membership[path] > 1} + shared_ranked = sorted(shared_direnv_only, key=lambda path: path_sizes.get(path, 0), reverse=True)[: args.top] + top_shared = [ + { + "path": path, + "nar_size_bytes": path_sizes.get(path, 0), + "retained_by_projects": sorted( + project for project, project_paths in project_closures.items() if path in project_paths + ), + } + for path in shared_ranked + ] + marginal_total = sum(item["marginal_unique_nar_bytes"] for item in projects) + artifact = { "format_version": 1, "generated_at": generated_at.isoformat(), @@ -159,7 +174,10 @@ def main() -> int: "all_direnv_closure_nar_bytes": total(all_direnv_closure), "collectively_direnv_only_nar_bytes": total(direnv_only), "collectively_direnv_only_path_count": len(direnv_only), + "marginal_unique_total_nar_bytes": marginal_total, + "shared_direnv_only_nar_bytes": total(shared_direnv_only), "top_collectively_direnv_only_paths": top_paths(direnv_only, path_sizes, args.top), + "top_shared_direnv_only_paths": top_shared, "projects": projects, } @@ -181,6 +199,8 @@ def main() -> int: print(f"Projects: {artifact['direnv_project_count']}") print(f"All direnv closures: {human_size(artifact['all_direnv_closure_nar_bytes'])}") print(f"Collectively direnv-only: {human_size(artifact['collectively_direnv_only_nar_bytes'])}") + print(f"Sum of project marginal unique: {human_size(artifact['marginal_unique_total_nar_bytes'])}") + print(f"Shared by multiple direnv projects only: {human_size(artifact['shared_direnv_only_nar_bytes'])}") print() print(f"{'MARGINAL':>11} {'CLOSURE':>11} {'AGE(d)':>8} PROJECT") for item in projects[: args.top]: