Skip to content

Commit faf5f81

Browse files
NiveditJainclaude
andauthored
[luv-23] Add --safe to luv --clean -f (spare folders <24h) (#23)
* [luv-23] Add --safe to luv --clean -f to spare folders younger than 24h Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * [luv-23] Reject --safe outside --clean -f instead of silently ignoring Per CodeRabbit review on #23. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 5e7bb8b commit faf5f81

3 files changed

Lines changed: 21 additions & 6 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
6464
| `luv [org/]<repo> -pr <number> [prompt]` | Open a PR by repo + number |
6565
| `luv --clean` | Delete workspaces where the branch is fully pushed/merged |
6666
| `luv --clean -f` | Force delete all workspaces |
67+
| `luv --clean --safe -f` | Force delete only workspaces older than 24h |
6768

6869
### Flags
6970

@@ -75,6 +76,7 @@ All workspaces live under `~/prs/`. The number comes from the repo's GitHub issu
7576
| `-nit` | Non-interactive: run `claude -p <prompt>` and exit (no REPL); streams `stream-json` events to stdout |
7677
| `-e` | Env: pass `LUV_*` environment variables (with prefix stripped) into the session |
7778
| `-f`, `--force` | Skip safety checks (with `--clean`) |
79+
| `--safe` | With `--clean -f`, only delete workspaces older than 24h (mtime) |
7880

7981
## Docker dev environments
8082

@@ -134,7 +136,7 @@ Docker mode works with all flags: `-n` opens a bash shell in the container, `-r`
134136
- No unpushed commits
135137
- If the remote branch is gone, verifies the PR was merged and local HEAD matches
136138

137-
Use `luv --clean -f` to skip all safety checks and delete everything.
139+
Use `luv --clean -f` to skip all safety checks and delete everything. Add `--safe` (i.e. `luv --clean --safe -f`) to restrict force-delete to workspaces whose folder mtime is older than 24 hours, leaving recently-touched workspaces alone.
138140

139141
## Configuration
140142

luv/__init__.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import subprocess
77
import sys
88
import tempfile
9+
import time
910
from pathlib import Path
1011

1112
LUV_DIR = Path.home() / ".luv"
@@ -391,14 +392,18 @@ def launch(clone_dir: Path, prompt: str | None, plan_mode: bool = False,
391392
os.execv(claude_bin, [claude_bin] + common_flags + mode_flags + initial_args)
392393

393394

394-
def cmd_clean(force: bool = False) -> None:
395+
SAFE_AGE_SECONDS = 24 * 3600
396+
397+
398+
def cmd_clean(force: bool = False, safe: bool = False) -> None:
395399
"""Scan ~/prs/ and delete fully-pushed, clean work folders."""
396400
if not PRS_DIR.exists():
397401
print("luv: nothing to clean (~/prs/ does not exist)")
398402
return
399403

400404
cleaned: list[str] = []
401405
skipped: list[tuple[str, str]] = []
406+
now = time.time()
402407

403408
for entry in sorted(PRS_DIR.iterdir()):
404409
if not entry.is_dir():
@@ -409,6 +414,9 @@ def cmd_clean(force: bool = False) -> None:
409414
continue # doesn't match {repo}-{number} — skip silently
410415

411416
if force:
417+
if safe and (now - entry.stat().st_mtime) < SAFE_AGE_SECONDS:
418+
skipped.append((entry.name, "younger than 24h (--safe)"))
419+
continue
412420
shutil.rmtree(entry)
413421
cleaned.append(entry.name)
414422
continue
@@ -589,8 +597,9 @@ def main() -> None:
589597
plan_mode = "-p" in args
590598
non_interactive = "-nit" in args
591599
force = "-f" in args or "--force" in args
600+
safe = "--safe" in args
592601
env_mode = "-e" in args
593-
args = [a for a in args if a not in ("-n", "-r", "-e", "-f", "--force", "-p", "-nit")]
602+
args = [a for a in args if a not in ("-n", "-r", "-e", "-f", "--force", "-p", "-nit", "--safe")]
594603
extra_env = collect_luv_env() if env_mode else {}
595604

596605
if not args or args[0] in ("-h", "--help"):
@@ -604,6 +613,7 @@ def main() -> None:
604613
-nit non-interactive: run claude -p <prompt> and exit (no REPL)
605614
-e env: pass LUV_* environment variables (with prefix stripped) into the session
606615
-f, --force (with --clean) skip safety checks and delete all work folders
616+
--safe (with --clean -f) only delete folders older than 24h
607617
608618
Commands:
609619
luv --init configure default GitHub org
@@ -613,7 +623,7 @@ def main() -> None:
613623
luv [org/]<repo> -pr <number> [prompt] open a GitHub PR by repo + number
614624
luv [org/]<repo> -n open shell in latest local clone
615625
luv [org/]<repo> -r resume Claude in latest local clone
616-
luv --clean [-f] delete fully-pushed work folders
626+
luv --clean [-f] [--safe] delete fully-pushed work folders
617627
618628
Org resolution:
619629
Explicit org/repo overrides the default. Run 'luv --init' to set a default.
@@ -625,8 +635,11 @@ def main() -> None:
625635
"dev-environment" service. Torn down automatically on exit.""")
626636
sys.exit(0)
627637

638+
if safe and (args[0] != "--clean" or not force):
639+
die("--safe only works with --clean -f")
640+
628641
if args[0] == "--clean":
629-
cmd_clean(force=force)
642+
cmd_clean(force=force, safe=safe)
630643
return
631644

632645
if args[0] == "--init":

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "luv-cli"
7-
version = "0.0.16"
7+
version = "0.0.17"
88
description = "Launch Claude Code agents on GitHub repos with isolated workspaces and optional Docker dev environments"
99
requires-python = ">=3.10"
1010
license = "MIT"

0 commit comments

Comments
 (0)