blob: 4189851d00b23757232db8c3c0df9178c4a6f115 [file]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Filesystem paths and directory discovery for AI coding agents."""
from __future__ import annotations
import os
import pathlib
import subprocess
__all__ = [
"find_checkout_git_repos",
"find_config_dirs",
"find_fuchsia_dir",
"find_permission_dirs",
"get_git_dir",
"get_hooks_dir",
"get_repo_root",
]
def _search_upwards_for_root(path: pathlib.Path) -> pathlib.Path | None:
current = path.resolve()
while True:
if (current / ".jiri_root").is_dir() or (
current / ".fx-root"
).is_file():
return current
if current == current.parent:
return None
current = current.parent
def find_fuchsia_dir(start_dir: pathlib.Path | None = None) -> pathlib.Path:
"""Locate the Fuchsia source root directory."""
if start_dir is not None:
root = _search_upwards_for_root(start_dir)
if root is not None:
return root
raise RuntimeError(
f"Could not locate Fuchsia root directory from start directory: {start_dir}"
)
env_dir = os.environ.get("FUCHSIA_DIR")
if env_dir:
candidate = pathlib.Path(env_dir).resolve()
if candidate.is_dir() and (
(candidate / ".jiri_root").is_dir()
or (candidate / ".fx-root").is_file()
):
return candidate
root = _search_upwards_for_root(pathlib.Path(__file__).parent)
if root is not None:
return root
raise RuntimeError(
"Could not locate Fuchsia root directory. Run within a Fuchsia source checkout or set FUCHSIA_DIR."
)
def find_checkout_git_repos(fuchsia_dir: pathlib.Path) -> list[pathlib.Path]:
"""Find all active Git repositories within the checkout (root, vendor/*, and integration)."""
repos: list[pathlib.Path] = []
if (fuchsia_dir / ".git").exists():
repos.append(fuchsia_dir)
vendor_dir = fuchsia_dir / "vendor"
if vendor_dir.is_dir():
for vendor_child in sorted(vendor_dir.iterdir()):
if vendor_child.is_dir() and (vendor_child / ".git").exists():
repos.append(vendor_child)
integration_dir = fuchsia_dir / "integration"
if (integration_dir / ".git").exists():
repos.append(integration_dir)
return repos
def find_config_dirs(fuchsia_dir: pathlib.Path) -> list[pathlib.Path]:
"""Find all agent config directories (public root + vendor extensions)."""
candidates: list[pathlib.Path] = []
root_cfg = fuchsia_dir / ".agents" / "config"
if root_cfg.is_dir():
candidates.append(root_cfg)
vendor_dir = fuchsia_dir / "vendor"
# Note: Assumes standard single-level vendor layout (vendor/<name>/.agents/config).
# If nested vendor repositories are introduced in the future, recursive search or manifest
# discovery can be considered.
if vendor_dir.is_dir():
for vendor_child in sorted(vendor_dir.iterdir()):
if vendor_child.is_dir():
cfg_dir = vendor_child / ".agents" / "config"
if cfg_dir.is_dir():
candidates.append(cfg_dir)
return candidates
def find_permission_dirs(fuchsia_dir: pathlib.Path) -> list[pathlib.Path]:
"""Find all permission config directories (public root + vendor extensions)."""
candidates: list[pathlib.Path] = []
for cfg_dir in find_config_dirs(fuchsia_dir):
perm_dir = cfg_dir / "permissions"
if perm_dir.is_dir():
candidates.append(perm_dir)
return candidates
def _git_rev_parse(repo_dir: pathlib.Path, *args: str) -> str:
res = subprocess.run(
["git", "rev-parse", *args],
cwd=repo_dir,
capture_output=True,
text=True,
)
if res.returncode != 0 or not res.stdout.strip():
raise RuntimeError(f"Directory is not in a git repository: {repo_dir}")
return res.stdout.strip()
def get_git_dir(repo_dir: pathlib.Path) -> pathlib.Path:
"""Returns the .git directory path for the given repository or worktree.
Args:
repo_dir: Directory to check from.
Returns:
Path to the git directory (worktree-specific .git directory if in a worktree).
Raises:
RuntimeError: If repo_dir is not inside a git repository.
"""
return (repo_dir / _git_rev_parse(repo_dir, "--git-dir")).resolve()
def get_hooks_dir(repo_dir: pathlib.Path) -> pathlib.Path:
"""Returns the git hooks directory path (resolving worktrees and common git dir).
Args:
repo_dir: Directory to check from.
Returns:
Path to the git hooks directory.
Raises:
RuntimeError: If repo_dir is not inside a git repository.
"""
return (
repo_dir / _git_rev_parse(repo_dir, "--git-path", "hooks")
).resolve()
def get_repo_root(repo_dir: pathlib.Path) -> pathlib.Path:
"""Returns the root directory of the git repository.
Args:
repo_dir: Directory to check from.
Returns:
Path to the repository root directory.
Raises:
RuntimeError: If repo_dir is not inside a git repository.
"""
return pathlib.Path(_git_rev_parse(repo_dir, "--show-toplevel")).resolve()