From 0d9d2e870d9a7f5eafc329ecc06ab2b18d2274a7 Mon Sep 17 00:00:00 2001 From: Natalie Date: Wed, 13 May 2026 15:30:46 -0700 Subject: [PATCH 1/2] =?UTF-8?q?feat(@ml/auto-commit-service):=20=E2=9C=A8?= =?UTF-8?q?=20add=20ignore-repo=20config=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- commits-tray | 55 +++++++++++++++++++++ src/auto_commit_service/tray/app.py | 13 ++++- src/auto_commit_service/tray/local_agent.py | 13 ++++- src/auto_commit_service/tray/local_git.py | 28 ++++++++++- 4 files changed, 104 insertions(+), 5 deletions(-) diff --git a/commits-tray b/commits-tray index 56b3152..865ad24 100755 --- a/commits-tray +++ b/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, ) diff --git a/src/auto_commit_service/tray/app.py b/src/auto_commit_service/tray/app.py index 79875aa..01e9862 100644 --- a/src/auto_commit_service/tray/app.py +++ b/src/auto_commit_service/tray/app.py @@ -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() diff --git a/src/auto_commit_service/tray/local_agent.py b/src/auto_commit_service/tray/local_agent.py index 351ed0f..1144671 100644 --- a/src/auto_commit_service/tray/local_agent.py +++ b/src/auto_commit_service/tray/local_agent.py @@ -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() diff --git a/src/auto_commit_service/tray/local_git.py b/src/auto_commit_service/tray/local_git.py index 7582e29..622b712 100644 --- a/src/auto_commit_service/tray/local_git.py +++ b/src/auto_commit_service/tray/local_git.py @@ -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 From 22cd4d08ed66a0e5404830c8d03acb9abd21bbeb Mon Sep 17 00:00:00 2001 From: Natalie Date: Mon, 1 Jun 2026 03:18:03 -0600 Subject: [PATCH 2/2] =?UTF-8?q?fix(@ml/auto-commit-service):=20?= =?UTF-8?q?=F0=9F=90=9B=20update=20default=20branch=20to=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- src/auto_commit_service/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auto_commit_service/config.py b/src/auto_commit_service/config.py index 131adbb..8e3d246 100644 --- a/src/auto_commit_service/config.py +++ b/src/auto_commit_service/config.py @@ -105,7 +105,7 @@ class AutoCommitSettings(BaseServiceSettings): # type: ignore[misc] # Git settings git_remote: str = Field(default="origin", description="Git remote to push to") - git_branch: str = Field(default="master", description="Git branch to push to") + git_branch: str = Field(default="main", description="Git branch to push to") co_authors: list[str] = Field( default=["Lilith Autocommit "], description="Co-author trailers appended to every commit message",