Skip to content

Commit 186d399

Browse files
michaeldtimpeclaude
andcommitted
config: route /review clones into local_cache_dir (default local-cache/)
/review and /refactor previously cloned into Path.cwd() — running luxe from the project root scattered repo clones (elara, neon-rain, zoleb, never-say-yes) at /luxe/<name>/. They were gitignored individually but littered the project tree. LuxeConfig.local_cache_dir (default "local-cache") plus LuxeConfig.cache_dir() helper expand the path (absolute, ~/, or relative to cwd) and ensure the directory exists. Both resolve_repo callsites in luxe_cli/repl/review.py and luxe_cli/review.py now pass cfg.cache_dir() instead of Path.cwd(). .gitignore: replace the per-repo entries (/ecliptic/, /luxe/zoleb/, /luxe/elara/, etc.) with a single `local-cache/` rule. Sibling projects /ecliptic/ and /warrens/ that aren't luxe-managed get their own short ignore block. Cleaned up the existing local tree: moved luxe/{elara,neon-rain, never-say-yes,zoleb} → luxe/local-cache/. Nothing was tracked in those dirs, so the move is a pure filesystem reorg. agents.yaml documents the new field with a one-line comment + points users at ~/.luxe/cache as an alternative. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f18e586 commit 186d399

8 files changed

Lines changed: 104 additions & 16 deletions

File tree

.gitignore

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,14 @@ luxe/scripts/results/
7575
# Uv / pip local state
7676
.uv/
7777

78-
# Local clones pulled in by `/review` and `/refactor`. These target
79-
# external repos the user happens to be analyzing; their source of
80-
# truth is elsewhere. Add new subdirectories here when /review clones
81-
# a new target into the cwd.
78+
# Local clones pulled in by `/review` and `/refactor`. Configured via
79+
# LuxeConfig.local_cache_dir (defaults to "local-cache" relative to
80+
# the cwd luxe was invoked from). One blanket rule replaces the
81+
# previous per-repo entries; set local_cache_dir to ~/.luxe/cache or
82+
# elsewhere if you'd rather keep clones outside the project tree.
83+
local-cache/
84+
85+
# Sibling projects that share this workspace but aren't part of luxe
86+
# (Godot game, personal writing). Not luxe-managed clones.
8287
/ecliptic/
83-
/luxe/zoleb/
84-
/luxe/elara/
85-
/luxe/never-say-yes/
86-
/luxe/neon-rain/
87-
88-
# Personal writing outside any tracked project (story drafts, outlines).
8988
/warrens/

ARCHITECTURE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,11 @@ Missing binaries or missing project markers produce a helpful
211211

212212
`configs/agents.yaml` holds:
213213
- Top-level: `ollama_base_url` (legacy fallback URL),
214-
`draw_things_url`, `image_output_dir`, `session_dir`
214+
`draw_things_url`, `image_output_dir`, `session_dir`,
215+
`local_cache_dir` (where `/review` and `/refactor` clone target
216+
repos — defaults to `local-cache` next to wherever luxe was launched;
217+
blanket-ignored in `.gitignore` so clones never accidentally land in
218+
a tracked tree)
215219
- `providers:` — named backend endpoints, e.g.
216220
`lmstudio: { base_url: "http://127.0.0.1:1234", kind: lmstudio }`.
217221
Agents reference these by key.

luxe/configs/agents.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ ollama_base_url: "http://127.0.0.1:11434"
77
draw_things_url: "http://127.0.0.1:7859" # Draw Things HTTP server port
88
image_output_dir: "~/luxe-images"
99
session_dir: "~/.luxe/sessions"
10+
# Where /review and /refactor clone target repos. Default is
11+
# `local-cache` relative to wherever you launched luxe; set to
12+
# `~/.luxe/cache` (or any absolute path) to keep clones out of the
13+
# project tree entirely.
14+
local_cache_dir: "local-cache"
1015

1116
# Named backend providers. Agents reference these by key via the
1217
# `provider:` field. Each agent may also set `endpoint:` directly to

luxe/luxe_cli/README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,13 @@ forceful and reconciles state to `aborted`.
322322

323323
## Code intelligence — `/review` and `/refactor`
324324

325-
Both take a git URL. They scan the **current folder** for an existing
326-
clone matching the URL's `origin` (pulled via `git pull --ff-only`);
327-
if none found, they `git clone` into the cwd as a subdirectory.
325+
Both take a git URL. They scan **`local_cache_dir`** (default
326+
`local-cache/` under cwd) for an existing clone matching the URL's
327+
`origin` and `git pull --ff-only` it; otherwise `git clone` into
328+
`local_cache_dir/<repo-name>`. Set `local_cache_dir: ~/.luxe/cache`
329+
in `agents.yaml` to keep clones outside the project tree entirely.
330+
The default path is in `.gitignore` so clones never accidentally
331+
land in a tracked repo.
328332

329333
The runtime wraps the repo in a background task that pins every
330334
subtask to the dedicated agent:

luxe/luxe_cli/registry.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ class LuxeConfig(BaseModel):
103103
session_dir: str = "~/.luxe/sessions"
104104
draw_things_url: str = "http://127.0.0.1:7860"
105105
image_output_dir: str = "~/luxe-images"
106+
# Where /review and /refactor clone target repos. Path is resolved
107+
# relative to cwd unless absolute or using ~. Default keeps clones
108+
# out of the project root (and out of git, via .gitignore) instead
109+
# of dropping them next to the user's source tree.
110+
local_cache_dir: str = "local-cache"
106111
# Deterministic pre-router: when enabled, a keyword/regex scorer
107112
# short-circuits the LLM router on decisive prompts. Falls through
108113
# to the LLM on low-confidence decisions. Disable for A/B testing.
@@ -119,6 +124,19 @@ class LuxeConfig(BaseModel):
119124
default_provider: str | None = None
120125
agents: list[AgentConfig]
121126

127+
def cache_dir(self) -> Path:
128+
"""Resolve local_cache_dir to an absolute path and ensure it exists.
129+
130+
Absolute paths and ~/ paths are honored as-given; relative
131+
paths resolve against the current working directory. The
132+
directory is created if missing — clones land directly inside.
133+
"""
134+
p = Path(self.local_cache_dir).expanduser()
135+
if not p.is_absolute():
136+
p = Path.cwd() / p
137+
p.mkdir(parents=True, exist_ok=True)
138+
return p
139+
122140
def get(self, name: str) -> AgentConfig:
123141
for a in self.agents:
124142
if a.name == name:

luxe/luxe_cli/repl/review.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def _start_review(url: str, mode: str, state: "ReplState", cfg: LuxeConfig) -> N
2222

2323
console.print(f"[dim]resolving[/dim] [cyan]{url}[/cyan][dim]...[/dim]")
2424
with console.status("[dim]git[/dim]", spinner="dots"):
25-
repo_path, status_msg = resolve_repo(url, Path.cwd())
25+
repo_path, status_msg = resolve_repo(url, cfg.cache_dir())
2626
if repo_path is None:
2727
console.print(f"[red]{status_msg}[/red]")
2828
return

luxe/luxe_cli/review.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def start_review_task(
8080
"""Plan + persist + spawn a review/refactor task. Returns task id.
8181
8282
Headless — no ReplState needed. Used by `luxe analyze --review`."""
83-
repo_path, status_msg = resolve_repo(str(url_or_path), Path.cwd())
83+
repo_path, status_msg = resolve_repo(str(url_or_path), cfg.cache_dir())
8484
if repo_path is None:
8585
raise RuntimeError(status_msg)
8686

luxe/tests/test_cache_dir.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""Tests for LuxeConfig.cache_dir() resolution + dispatch wiring."""
2+
3+
from __future__ import annotations
4+
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
from luxe_cli.registry import LuxeConfig
10+
11+
12+
def _cfg(**overrides) -> LuxeConfig:
13+
base = dict(agents=[])
14+
base.update(overrides)
15+
return LuxeConfig(**base)
16+
17+
18+
def test_cache_dir_default_is_local_cache_under_cwd(tmp_path, monkeypatch):
19+
monkeypatch.chdir(tmp_path)
20+
cfg = _cfg()
21+
p = cfg.cache_dir()
22+
assert p == tmp_path / "local-cache"
23+
assert p.is_dir()
24+
25+
26+
def test_cache_dir_honors_absolute_path(tmp_path):
27+
target = tmp_path / "elsewhere"
28+
cfg = _cfg(local_cache_dir=str(target))
29+
p = cfg.cache_dir()
30+
assert p == target
31+
assert p.is_dir()
32+
33+
34+
def test_cache_dir_expands_tilde(tmp_path, monkeypatch):
35+
monkeypatch.setenv("HOME", str(tmp_path))
36+
cfg = _cfg(local_cache_dir="~/my-cache")
37+
p = cfg.cache_dir()
38+
assert p == tmp_path / "my-cache"
39+
assert p.is_dir()
40+
41+
42+
def test_cache_dir_creates_missing_dir(tmp_path):
43+
target = tmp_path / "fresh" / "nested"
44+
cfg = _cfg(local_cache_dir=str(target))
45+
assert not target.exists()
46+
cfg.cache_dir()
47+
assert target.is_dir()
48+
49+
50+
def test_real_agents_yaml_resolves_local_cache(tmp_path, monkeypatch):
51+
"""The shipped config defaults to local-cache/, not cwd."""
52+
monkeypatch.chdir(tmp_path)
53+
from luxe_cli.registry import load_config
54+
55+
cfg = load_config()
56+
p = cfg.cache_dir()
57+
assert p.name == "local-cache"
58+
assert p.parent == tmp_path

0 commit comments

Comments
 (0)