Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions fixos/cli/_auto_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
".cache/pypoetry/cache",
".cache/pypoetry/artifacts",
".cache/Google/Chrome/Default/Cache",
".cache/go-build",
".cache/ruff",
".cache/mypy",
".cache/Flake8",
".cache/black",
)
_BROWSERS = ("google-chrome", "chromium", "BraveSoftware/Brave-Browser")
_BROWSER_LEAVES = ("Cache", "Code Cache", "GPUCache", "DawnCache", "ShaderCache")
Expand Down
34 changes: 33 additions & 1 deletion fixos/diagnostics/quick_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,12 @@ class CacheRule:
CacheRule(
"pnpm",
"pnpm store",
("~/.local/share/pnpm/store", "~/.pnpm-store"),
(
"~/.local/share/pnpm/store",
"~/.pnpm-store",
"~/.cache/pnpm",
"~/Library/Caches/pnpm",
),
"pnpm store prune",
stack="node",
),
Expand All @@ -95,6 +100,33 @@ class CacheRule:
"poetry cache clear --all pypi",
stack="python",
),
CacheRule(
"go-build",
"Go build cache",
(
"~/.cache/go-build",
"~/Library/Caches/go-build",
"~/AppData/Local/go-build",
),
"go clean -cache",
stack="go",
),
CacheRule(
"python-linters",
"Python linters and typecheck cache",
(
"~/.cache/ruff",
"~/.cache/mypy",
"~/.cache/Flake8",
"~/.cache/flake8",
"~/.cache/black",
"~/Library/Caches/ruff",
"~/Library/Caches/mypy",
"~/AppData/Local/ruff/Cache",
),
"rm -rf ~/.cache/ruff ~/.cache/mypy ~/.cache/Flake8 ~/.cache/black",
stack="python",
),
CacheRule(
"chrome",
"Chrome cache",
Expand Down
1 change: 1 addition & 0 deletions project/TICKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ This file indexes governance tickets without taking ownership of
| **ticket-103** | [`README.md`](./ticket-103/README.md) | - | - | - | - | - |
| **ticket-104** | [`README.md`](./ticket-104/README.md) | - | - | - | - | - |
| **ticket-105** | [`README.md`](./ticket-105/README.md) | - | - | - | - | - |
| **ticket-106** | [`README.md`](./ticket-106/README.md) | - | - | - | - | - |
| **ticket-107** | [`README.md`](./ticket-107/README.md) | - | - | - | - | - |
| **ticket-108** | [`README.md`](./ticket-108/README.md) | - | - | - | - | - |
<!-- AUTO:TICKET_INDEX:END -->
22 changes: 22 additions & 0 deletions project/ticket-106/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Ticket 106: Expand additional_cache_candidates and quick_snapshot CACHE_RULES for developer caches

- **ID**: ticket-106
- **Owner**: agent:antigravity
- **Status**: IN_PROGRESS
- **Workflow state**: EDIT
- **Created**: 2026-10-26

## Goal and scope

Expand `_CACHE_PATHS` in `fixos/cli/_auto_cleanup.py` and `CACHE_RULES` in `fixos/diagnostics/quick_snapshot.py` to cover regenerable developer caches (`.cache/go-build`, `.cache/ruff`, `.cache/mypy`, `.cache/Flake8`, `.cache/black`, and `~/.cache/pnpm`) while ensuring strict protections against symlinks, nested mounts, browser profiles, and model weights (`.cache/huggingface`). Unit tests in `tests/unit/test_auto_cleanup.py` verify that developer caches are reclaimed and protected paths remain untouched.

## Acceptance criteria

- [x] AC-01: Add safe regenerable developer cache leaves (`.cache/go-build`, `.cache/ruff`, `.cache/mypy`, `.cache/Flake8`, `.cache/black`) to `_CACHE_PATHS` in `fixos/cli/_auto_cleanup.py` preserving strict `cache_path_allowed` guards.
- [x] AC-02: Align `CACHE_RULES` in `fixos/diagnostics/quick_snapshot.py` so `go-build` and Python linter/typecheck caches are reflected in `safe_reclaim` estimates.
- [x] AC-03: Unit tests in `tests/unit/test_auto_cleanup.py` verify that new regenerable developer caches are cleaned and protected paths (models, browser data) remain untouched.

## Tracking boundary

This directory contains the minimal reviewed intent. Optional participant prose
and raw command logs are not required delivery output.
82 changes: 82 additions & 0 deletions project/ticket-106/intent.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"schema": "new-project.intent/v3",
"ticket": "ticket-106",
"summary": "Expand additional_cache_candidates and quick_snapshot CACHE_RULES for developer caches",
"workstream": "application",
"classification": {
"kind": "FEATURE",
"priority": "P2",
"origin": "requested"
},
"allowedPaths": [
"project/ticket-106/**",
"TODO.md",
"project/TICKETS.md",
"fixos/cli/_auto_cleanup.py",
"fixos/diagnostics/quick_snapshot.py",
"tests/unit/test_auto_cleanup.py"
],
"forbiddenPaths": [
"project/ticket-*/user-*.md"
],
"stacks": [
"python"
],
"dependsOn": [],
"conflictsWith": [],
"integrationTicket": null,
"delivery": {
"acceptedBaseSha": "98e8f0214df0900ff3dc16b8a42a102de5e67e3e",
"targetBranch": "main",
"outcome": "Expand _CACHE_PATHS in fixos/cli/_auto_cleanup.py and CACHE_RULES in fixos/diagnostics/quick_snapshot.py to cover safe regenerable developer caches (.cache/go-build, .cache/ruff, .cache/mypy, .cache/Flake8, .cache/black) while preserving strict path guards for model weights and browser profile data, with comprehensive unit tests.",
"nonGoals": [
"Never touch model weights in .cache/huggingface or browser profile databases/bookmarks/cookies."
],
"complexity": "S",
"estimatedMinutes": 30,
"budgets": {
"maxImplementationFiles": 3,
"maxAffectedComponents": 2,
"maxPublicInterfaceChanges": 0,
"maxRuntimeDependencies": 0
},
"architecture": {
"status": "accepted",
"decision": "Add safe regenerable developer cache leaf paths (.cache/go-build, .cache/ruff, .cache/mypy, .cache/Flake8, .cache/black) to _CACHE_PATHS in _auto_cleanup.py and align CACHE_RULES in quick_snapshot.py with go-build and python-linters rules plus pnpm ~/.cache/pnpm coverage; verify path protections and safe_reclaim accounting in unit tests.",
"components": [
{
"name": "developer-cache-cleanup",
"paths": [
"fixos/cli/_auto_cleanup.py",
"fixos/diagnostics/quick_snapshot.py"
]
},
{
"name": "tests",
"paths": [
"tests/unit/test_auto_cleanup.py"
]
}
],
"responsibilityChanges": false,
"interfaceChanges": [],
"dataChanges": [],
"ui": {
"impact": "none",
"states": [],
"evidence": []
},
"rollback": "Revert ticket-106 commit via protected publication."
},
"runtimeDependencies": [],
"validation": [
{
"criterion": "AC-01",
"commands": [
"pytest tests/unit/test_auto_cleanup.py tests/unit/test_quick_snapshot.py -q"
],
"evidence": "project/ticket-106/README.md"
}
]
}
}
126 changes: 121 additions & 5 deletions tests/unit/test_auto_cleanup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Safety and reporting regressions for expanded autonomous cleanup."""

import inspect
import json
import subprocess
from pathlib import Path
Expand All @@ -14,6 +15,12 @@
from fixos.cli.main import cli


def _cli_runner() -> CliRunner:
if "mix_stderr" in inspect.signature(CliRunner.__init__).parameters:
return CliRunner(mix_stderr=False)
return CliRunner()


def put(home, relative, content="cached"):
path = home / relative
path.parent.mkdir(parents=True, exist_ok=True)
Expand All @@ -32,6 +39,11 @@ def test_cleanup_keeps_downloads_profiles_models_and_unknown_cache(
".cache/pypoetry/artifacts/pkg.whl",
".cache/google-chrome/Default/Cache/data",
".cargo/registry/cache/repo/a.crate",
".cache/go-build/leaf/cache",
".cache/ruff/leaf/cache",
".cache/mypy/leaf/cache",
".cache/Flake8/leaf/cache",
".cache/black/leaf/cache",
)
]
protected = [
Expand Down Expand Up @@ -168,8 +180,8 @@ def auto_cli(monkeypatch):
"thumbnails",
):
monkeypatch.setattr(space, "_cleanup_" + name, Mock())
extra = Mock(side_effect=lambda **k: print("cache progress"))
docker = Mock(side_effect=lambda **k: print("docker progress"))
extra = Mock(side_effect=lambda **k: print("cache progress", flush=True))
docker = Mock(side_effect=lambda **k: print("docker progress", flush=True))
monkeypatch.setattr(auto, "cleanup_additional_caches", extra)
monkeypatch.setattr(auto, "cleanup_docker_builder", docker)
return extra, docker
Expand Down Expand Up @@ -204,7 +216,7 @@ def stat(path, *args, **kwargs):
return stat_original(path, *args, **kwargs)

monkeypatch.setattr(os, "stat", stat)
result = CliRunner().invoke(cli, ["auto", "--json"])
result = _cli_runner().invoke(cli, ["auto", "--json"])
assert result.exit_code == 0, result.output
payload = json.loads(result.stdout)
assert payload["disk_freed_bytes"] == 1024**3
Expand Down Expand Up @@ -291,7 +303,7 @@ def test_interactive_cache_still_removes_selected_regular_unknown_cache(


def test_auto_executes_7d_state_and_projects_cleanup_by_default(auto_cli):
result = CliRunner().invoke(cli, ["auto", "--json"])
result = _cli_runner().invoke(cli, ["auto", "--json"])
assert result.exit_code == 0, result.output
payload = json.loads(result.stdout)
assert "state-old" in payload["steps_executed"]
Expand All @@ -304,11 +316,115 @@ def test_auto_executes_7d_state_and_projects_cleanup_by_default(auto_cli):


def test_auto_respects_no_state_old_and_no_projects_old(auto_cli):
result = CliRunner().invoke(
result = _cli_runner().invoke(
cli, ["auto", "--json", "--no-state-old", "--no-projects-old"]
)
assert result.exit_code == 0, result.output
payload = json.loads(result.stdout)
assert "state-old" not in payload["steps_executed"]
assert "projects-old" not in payload["steps_executed"]


def test_cleanup_removes_developer_caches_and_preserves_models_and_browser_data(
tmp_path, monkeypatch
):
monkeypatch.setattr(Path, "home", lambda: tmp_path)
caches = [
put(tmp_path, p)
for p in (
".cache/go-build/leaf/cache",
".cache/ruff/leaf/cache",
".cache/mypy/leaf/cache",
".cache/Flake8/leaf/cache",
".cache/black/leaf/cache",
)
]
protected = [
put(tmp_path, p)
for p in (
".cache/huggingface/hub/models--bert-base-uncased/model.safetensors",
".cache/google-chrome/Default/History",
".cache/google-chrome/Default/Cookies",
".config/google-chrome/Default/Bookmarks",
".cache/unknown_tool/cache",
)
]
auto.cleanup_additional_caches(dry_run=False, yes=True)
assert all(not p.exists() for p in caches)
assert all(p.exists() for p in protected)


def test_additional_cache_candidates_includes_developer_caches(tmp_path):
for p in (
".cache/go-build/data",
".cache/ruff/data",
".cache/mypy/data",
".cache/Flake8/data",
".cache/black/data",
):
put(tmp_path, p, "data")
candidates = auto.additional_cache_candidates(tmp_path)
descriptions = {c["description"] for c in candidates}
assert "~/.cache/go-build" in descriptions
assert "~/.cache/ruff" in descriptions
assert "~/.cache/mypy" in descriptions
assert "~/.cache/Flake8" in descriptions
assert "~/.cache/black" in descriptions


def test_quick_snapshot_reflects_go_and_python_caches_in_safe_reclaim(
tmp_path, monkeypatch
):
from datetime import datetime, timezone

from fixos.diagnostics import quick_snapshot as qs

put(tmp_path, ".cache/go-build/sample.bin", "x" * 2048)
put(tmp_path, ".cache/ruff/sample.bin", "y" * 4096)
put(tmp_path, ".cache/mypy/sample.bin", "z" * 1024)

monkeypatch.setattr(
qs,
"_resource_snapshot",
lambda: {
"cpu": {
"percent": 10,
"logical_cpus": 4,
"load": [0.1, 0.1, 0.1],
"load_1_normalized_percent": 2,
},
"memory": {
"total_bytes": 1000,
"used_bytes": 300,
"available_bytes": 700,
"percent": 30,
},
"swap": {"total_bytes": 1000, "used_bytes": 0, "percent": 0},
"disk": {
"mount": "/",
"total_bytes": 10 * 1024**3,
"used_bytes": 1024**3,
"free_bytes": 9 * 1024**3,
"percent": 10,
},
"top_processes": [],
},
)
monkeypatch.setattr(qs, "_cached_docker_review", lambda now, **_: None)

snapshot = qs.collect_quick_snapshot(
state_dir=tmp_path,
home=tmp_path,
cwd=tmp_path,
save=False,
now=datetime.now(timezone.utc),
)

safe_items = snapshot["safe_reclaim"]["items"]
item_ids = {item["id"] for item in safe_items}
assert "go-build" in item_ids
assert "python-linters" in item_ids
assert snapshot["safe_reclaim"]["estimated_max_bytes"] > 0



Loading