feat(@ml/auto-commit-service): ✨ add ignore-repo config support
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a905acc83d
commit
0d9d2e870d
4 changed files with 104 additions and 5 deletions
55
commits-tray
55
commits-tray
|
|
@ -12,6 +12,8 @@ Usage:
|
|||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
|
@ -22,6 +24,45 @@ _tray_dir = os.path.join(_script_dir, "src", "auto_commit_service", "tray")
|
|||
sys.path.insert(0, _tray_dir)
|
||||
|
||||
|
||||
def _load_ignore_repos_from_config() -> list[str]:
|
||||
"""Read ignore_repos from ~/.config/commits/startup-config.json.
|
||||
|
||||
Mirrors the daemon's config loader (see auto_commit_service.__main__).
|
||||
Supports either a top-level `ignore_repos` array (shared) or a per-daemon
|
||||
entry whose `directory` matches the current working dir. Missing or
|
||||
unreadable files yield an empty list — never raises.
|
||||
"""
|
||||
config_path = Path.home() / ".config" / "commits" / "startup-config.json"
|
||||
if not config_path.exists():
|
||||
return []
|
||||
try:
|
||||
data = json.loads(config_path.read_text())
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
logging.warning(f"commits-tray: failed to read {config_path}: {exc}")
|
||||
return []
|
||||
|
||||
ignore: list[str] = []
|
||||
if isinstance(data.get("ignore_repos"), list):
|
||||
ignore.extend(str(p) for p in data["ignore_repos"] if p)
|
||||
|
||||
cwd = str(Path.cwd())
|
||||
for daemon in data.get("daemons", []) or []:
|
||||
if isinstance(daemon, dict) and daemon.get("directory") == cwd:
|
||||
entries = daemon.get("ignore_repos")
|
||||
if isinstance(entries, list):
|
||||
ignore.extend(str(p) for p in entries if p)
|
||||
break
|
||||
|
||||
# De-dup while preserving order
|
||||
seen: set[str] = set()
|
||||
deduped: list[str] = []
|
||||
for p in ignore:
|
||||
if p not in seen:
|
||||
seen.add(p)
|
||||
deduped.append(p)
|
||||
return deduped
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="ACS menu bar app + local commit agent")
|
||||
parser.add_argument(
|
||||
|
|
@ -59,8 +100,21 @@ def main():
|
|||
default=131072,
|
||||
help="Per-repo diff size cap in bytes before truncation (default: 131072)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ignore-repo",
|
||||
action="append",
|
||||
default=[],
|
||||
dest="ignore_repos",
|
||||
help=(
|
||||
"Substring matched against absolute repo paths; matching repos "
|
||||
"are excluded from discovery. Repeat for multiple. Merged with "
|
||||
"ignore_repos from ~/.config/commits/startup-config.json."
|
||||
),
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
ignore_repos = list(_load_ignore_repos_from_config()) + list(args.ignore_repos or [])
|
||||
|
||||
# Import after argparse so --help exits cleanly before rumps is required
|
||||
from app import run_tray # noqa: E402
|
||||
|
||||
|
|
@ -72,6 +126,7 @@ def main():
|
|||
dry_run=args.dry_run,
|
||||
max_diff_bytes=args.max_diff_bytes,
|
||||
cycle_seconds=args.cycle,
|
||||
ignore_repos=ignore_repos,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class CommitsTrayApp(rumps.App):
|
|||
commit_local: bool = True,
|
||||
dry_run: bool = False,
|
||||
max_diff_bytes: int = 131072,
|
||||
ignore_repos: list[str] | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
name="Commits",
|
||||
|
|
@ -62,6 +63,7 @@ class CommitsTrayApp(rumps.App):
|
|||
self._daemon_url = daemon_url
|
||||
self._repos_paths = repos_paths or [Path.home() / "Code"]
|
||||
self._commit_local = commit_local
|
||||
self._ignore_repos = list(ignore_repos or [])
|
||||
|
||||
# Remote daemon client (for status + LLM health checks)
|
||||
self.client = DaemonClient(base_url=daemon_url)
|
||||
|
|
@ -73,6 +75,7 @@ class CommitsTrayApp(rumps.App):
|
|||
cycle_seconds=cycle_seconds,
|
||||
dry_run=dry_run,
|
||||
max_diff_bytes=max_diff_bytes,
|
||||
ignore_repos=self._ignore_repos,
|
||||
)
|
||||
|
||||
# For local commit history display
|
||||
|
|
@ -234,7 +237,11 @@ class CommitsTrayApp(rumps.App):
|
|||
def _update_commits_menu(self) -> None:
|
||||
"""Refresh the recent commits submenu from local git repos."""
|
||||
if not self._repos_discovered:
|
||||
self._local_repos = discover_repos(self._repos_paths, max_depth=4)
|
||||
self._local_repos = discover_repos(
|
||||
self._repos_paths,
|
||||
max_depth=4,
|
||||
ignore_repos=self._ignore_repos,
|
||||
)
|
||||
self._repos_discovered = True
|
||||
|
||||
if not self._local_repos:
|
||||
|
|
@ -322,6 +329,7 @@ def run_tray(
|
|||
commit_local: bool = True,
|
||||
dry_run: bool = False,
|
||||
max_diff_bytes: int = 131072,
|
||||
ignore_repos: list[str] | None = None,
|
||||
) -> None:
|
||||
"""Launch the menu bar application with local commit agent.
|
||||
|
||||
|
|
@ -333,6 +341,8 @@ def run_tray(
|
|||
Pass False to run tray in monitor-only mode (no local commits).
|
||||
dry_run: If True, scan and generate messages but skip git commit/push.
|
||||
max_diff_bytes: Per-repo diff size cap before truncation.
|
||||
ignore_repos: Substrings matched against absolute repo paths; any
|
||||
matching repo is excluded from discovery and never scanned.
|
||||
"""
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(name)s %(message)s")
|
||||
app = CommitsTrayApp(
|
||||
|
|
@ -342,5 +352,6 @@ def run_tray(
|
|||
commit_local=commit_local,
|
||||
dry_run=dry_run,
|
||||
max_diff_bytes=max_diff_bytes,
|
||||
ignore_repos=ignore_repos,
|
||||
)
|
||||
app.run()
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ class LocalCommitAgent:
|
|||
recovery_state_path: Path | None = None,
|
||||
dry_run: bool = False,
|
||||
max_diff_bytes: int = 131072,
|
||||
ignore_repos: list[str] | None = None,
|
||||
):
|
||||
self.acs_url = acs_url.rstrip("/")
|
||||
self.repos_paths = repos_paths
|
||||
|
|
@ -89,6 +90,7 @@ class LocalCommitAgent:
|
|||
self.co_author = co_author
|
||||
self.dry_run = dry_run
|
||||
self.max_diff_bytes = max_diff_bytes
|
||||
self.ignore_repos = list(ignore_repos or [])
|
||||
|
||||
self._client = httpx.Client(base_url=self.acs_url, timeout=30.0)
|
||||
self._running = False
|
||||
|
|
@ -171,8 +173,15 @@ class LocalCommitAgent:
|
|||
if self._running:
|
||||
return
|
||||
self._stop_event.clear()
|
||||
self._repos = discover_repos(self.repos_paths, max_depth=4)
|
||||
logger.info(f"Local agent starting: {len(self._repos)} repos, cycle={self.cycle_seconds}s")
|
||||
self._repos = discover_repos(
|
||||
self.repos_paths,
|
||||
max_depth=4,
|
||||
ignore_repos=self.ignore_repos,
|
||||
)
|
||||
logger.info(
|
||||
f"Local agent starting: {len(self._repos)} repos, "
|
||||
f"cycle={self.cycle_seconds}s, ignore_repos={self.ignore_repos}"
|
||||
)
|
||||
self._running = True
|
||||
self._thread = threading.Thread(target=self._loop, daemon=True)
|
||||
self._thread.start()
|
||||
|
|
|
|||
|
|
@ -43,8 +43,21 @@ def _is_usable_repo(path: Path) -> bool:
|
|||
return dot_git.is_file() # gitfile (worktree/submodule)
|
||||
|
||||
|
||||
def discover_repos(base_paths: list[Path], max_depth: int = 4) -> list[Path]:
|
||||
"""Find git repositories under the given base paths."""
|
||||
def discover_repos(
|
||||
base_paths: list[Path],
|
||||
max_depth: int = 4,
|
||||
ignore_repos: list[str] | None = None,
|
||||
) -> list[Path]:
|
||||
"""Find git repositories under the given base paths.
|
||||
|
||||
Args:
|
||||
base_paths: Directories to walk.
|
||||
max_depth: Maximum recursion depth.
|
||||
ignore_repos: Substrings matched against the absolute repo path; any
|
||||
repo whose path contains one of these strings is excluded. Mirrors
|
||||
the daemon's `git/discovery.py` semantics so the same
|
||||
startup-config entries work for both surfaces.
|
||||
"""
|
||||
repos: list[Path] = []
|
||||
|
||||
for base in base_paths:
|
||||
|
|
@ -52,6 +65,17 @@ def discover_repos(base_paths: list[Path], max_depth: int = 4) -> list[Path]:
|
|||
continue
|
||||
_walk_for_repos(base, repos, depth=0, max_depth=max_depth)
|
||||
|
||||
if ignore_repos:
|
||||
patterns = [p for p in ignore_repos if p]
|
||||
if patterns:
|
||||
filtered: list[Path] = []
|
||||
for repo in repos:
|
||||
repo_str = str(repo)
|
||||
if any(pattern in repo_str for pattern in patterns):
|
||||
continue
|
||||
filtered.append(repo)
|
||||
return filtered
|
||||
|
||||
return repos
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue