From aade1a60075fefaa16831d07481fad132bc22b7a Mon Sep 17 00:00:00 2001 From: scawful Date: Tue, 30 Dec 2025 09:05:19 -0500 Subject: [PATCH] core: root discovery + status --- src/afs/__init__.py | 10 +++++++++- src/afs/cli.py | 26 ++++++++++++++++++++++++++ src/afs/core.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/afs/core.py diff --git a/src/afs/__init__.py b/src/afs/__init__.py index b0dcfd8..f1cb4d9 100644 --- a/src/afs/__init__.py +++ b/src/afs/__init__.py @@ -3,6 +3,14 @@ __version__ = "0.0.0" from .config import load_config, load_config_model +from .core import find_root, resolve_context_root from .plugins import discover_plugins, load_plugins -__all__ = ["load_config", "load_config_model", "discover_plugins", "load_plugins"] +__all__ = [ + "load_config", + "load_config_model", + "discover_plugins", + "load_plugins", + "find_root", + "resolve_context_root", +] diff --git a/src/afs/cli.py b/src/afs/cli.py index b20d420..46454ed 100644 --- a/src/afs/cli.py +++ b/src/afs/cli.py @@ -8,6 +8,7 @@ from pathlib import Path from typing import Iterable from .config import load_config_model +from .core import find_root, resolve_context_root from .plugins import discover_plugins, load_plugins from .schema import AFSConfig, GeneralConfig, WorkspaceDirectory @@ -128,6 +129,27 @@ def _plugins_command(args: argparse.Namespace) -> int: return 0 +def _status_command(args: argparse.Namespace) -> int: + start_dir = Path(args.start_dir).expanduser().resolve() if args.start_dir else None + root = find_root(start_dir) + config = load_config_model() + context_root = resolve_context_root(config, root) + + print(f"context_root: {context_root}") + print(f"linked_root: {root if root else '(none)'}") + + missing = [] + for name in AFS_DIRS: + if not (context_root / name).exists(): + missing.append(name) + if missing: + print("missing_dirs: " + ", ".join(missing)) + else: + print("missing_dirs: (none)") + + return 0 + + def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(prog="afs") subparsers = parser.add_subparsers(dest="command") @@ -147,6 +169,10 @@ def build_parser() -> argparse.ArgumentParser: plugins_parser.add_argument("--load", action="store_true", help="Attempt to import plugins.") plugins_parser.set_defaults(func=_plugins_command) + status_parser = subparsers.add_parser("status", help="Show context root status.") + status_parser.add_argument("--start-dir", help="Directory to search from.") + status_parser.set_defaults(func=_status_command) + return parser diff --git a/src/afs/core.py b/src/afs/core.py new file mode 100644 index 0000000..6bfddb4 --- /dev/null +++ b/src/afs/core.py @@ -0,0 +1,34 @@ +"""Core AFS helpers.""" + +from __future__ import annotations + +import os +from pathlib import Path + +from .schema import AFSConfig + + +def find_root(start_dir: Path | None = None) -> Path | None: + """Find a .context directory by walking upward.""" + if start_dir is None: + start_dir = Path.cwd() + current = start_dir.resolve() + for parent in [current, *current.parents]: + candidate = parent / ".context" + if candidate.exists() and candidate.is_dir(): + return candidate + if (parent / "afs.toml").exists(): + return parent / ".context" + return None + + +def resolve_context_root(config: AFSConfig | None, linked_root: Path | None) -> Path: + """Resolve the active context root for this machine.""" + env_root = os.environ.get("AFS_CONTEXT_ROOT") + if env_root: + return Path(env_root).expanduser().resolve() + if linked_root: + return linked_root.resolve() + if config: + return config.general.context_root + return (Path.home() / ".context").resolve()