Skip to content

Latest commit

 

History

History
292 lines (233 loc) · 12.4 KB

usage.md

File metadata and controls

292 lines (233 loc) · 12.4 KB

Usage — API / CLI / MCP

repository-manager exposes the same capability three ways: as MCP tools an agent calls, as a Python API (Git) the operator import, and as a command-line interface.

As an MCP server

Once deployed, the server registers consolidated, action-routed tool modules. Each module groups related methods behind one tool to keep the LLM context small, and each can be toggled independently with its environment variable.

Module Toggle Default Action-routed methods
Misc MISCTOOL True health check and miscellaneous helpers
Git Operations GIT_OPERATIONSTOOL True clone, pull, push (legacy raw is permanently retired)
Workspace Management WORKSPACE_MANAGEMENTTOOL True list, list_branches, maintain, remediate, save, setup, template
Project Management PROJECT_MANAGEMENT_TOOL True build, install, validate, validate_status

Example agent prompts that map onto these tools:

  • "List every project in the workspace." → workspace_management list
  • "Pull the latest changes for all repositories." → git_operations pull
  • "Roll scion-sdk 0.6.4 to every connector." → rm_rollout (plan, apply, status, release)

As a Python API

Git (repository_manager.repository_manager) is a workspace-aware client for bulk Git operations and workspace introspection.

import os

from repository_manager.repository_manager import Git

git = Git(path=os.environ["REPOSITORY_MANAGER_WORKSPACE"])

# Reads
projects = git.get_workspace_projects()  # list of managed project names
project_map = git.get_project_map()  # name -> absolute path
branches = git.list_branches()  # name -> current branch

# Bulk operations
git.pull_projects()  # pull every managed repository
git.clone_projects(["scion-sdk"])  # clone selected projects

# Validation
result = git.validate_single_project(project_map["scion-sdk"])

Load a workspace from its declarative workspace.yml:

import os

git = Git(path=os.environ["REPOSITORY_MANAGER_WORKSPACE_ROOT"])
git.setup_from_yaml(os.environ["WORKSPACE_YML"])  # use the XDG-managed manifest

The packaged manifest contains only environment references for the workspace root, private Git origin, and deployment DNS suffix. Inject those values at runtime; the manifest never persists a user name, machine path, or private endpoint.

As a CLI

The repository-manager console script drives the full maintenance lifecycle from the command line.

# Set up the workspace from its declared configuration
repository-manager --setup

# Enumerate branches across every managed repository
repository-manager --branches

# Clone and pull in bulk
repository-manager --clone
repository-manager --pull

--validate runs a full pre-release validation across the workspace.

To roll a dependency floor across the fleet (one pull request per repository, auto-merge, resumable ledger, tag after merge), use the rollout command:

repository-manager rollout --dependency scion-sdk --floor 0.6.4 --dry-run
repository-manager rollout --dependency scion-sdk --floor 0.6.4
repository-manager rollout status --dependency scion-sdk --floor 0.6.4
repository-manager rollout release --dependency scion-sdk --floor 0.6.4

See Dependency rollout.

Lane lifecycle (CONCEPT:RM-LANE-DOCTOR)

When multiple agents and humans develop the same repositories at once, each unit of work runs as an isolated lane. The --lane verbs make that lifecycle executable rather than a convention:

# Open a lane: a worktree PLUS a partitioned cargo target dir, pytest basetemp,
# TMPDIR and PRE_COMMIT_HOME -- and a preflight that proves the isolation.
repository-manager --lane start --lane-repo scion-sdk --lane-branch lane/my-change

# Adopt the environment in your shell.
eval "$(repository-manager --lane env --lane-path . --lane-shell)"

# Diagnose a lane that is behaving impossibly. Mutates nothing, answers in <1s.
repository-manager --lane doctor --lane-path .

# Close it out: blocking preflight, then hand the branch to the merge queue.
repository-manager --lane finish --lane-path . --lane-base main

doctor is the one to reach for when a test fails in a way that cannot be true, a build will not go green, a merge keeps being refused, or work has gone missing. Each check reports its verdict, its evidence, and a literal remedy command; fail blocks finish, warn never does. What it checks, and why each check exists:

Check Failure it exists to catch
not-canonical editing a canonical checkout a background git reset can discard
no-worktree-venv a foreign or stale worktree-local .venv shadowing the workspace environment; the exact all-extras environment managed by scripts/uv_workspace.py is accepted
cargo-partition a shared CARGO_TARGET_DIR, which corrupts concurrent builds rather than merely serializing them
precommit-home the shared pre-commit store, where a crash inside staged_files_only()'s window loses unstaged work to an orphaned patch
pytest-basetemp concurrent lanes contending on one pytest temp root
shared-stash-ref refs/stash — one ref shared by every worktree of the repository
test-runner uv run pytest silently resolving the system interpreter
canonical-clean a dirty canonical tree, which blocks every lane's landing
merge-queue-config a repository declaring no gates, which the queue refuses rather than defaults
base-drift reasoning about the branch tip when the merged tree is what lands
committed-work uncommitted work, the only kind a tree reset can take

The same action core backs the rm_lane MCP tool and python -m repository_manager.lane_doctor, so the surfaces cannot drift.

Working-tree mutation safety (CONCEPT:RM-SAFE-COMMIT, CONCEPT:RM-DESTRUCTIVE-GUARD, CONCEPT:RM-TREE-REPAIR)

The pure safety primitives are available to lane and job implementations without changing MCP registration:

from repository_manager.destructive_guard import guard, issue_override_token
from repository_manager.safe_commit import safe_commit
from repository_manager.tree_repair import diagnose, repair

commit = safe_commit("/path/to/lane", "implement the reviewed change")
finding = diagnose("/path/to/lane")
if finding["finding"] != "clean":
    repair("/path/to/lane", finding=finding)

# Destructive argv is refused unless this explicit token is consumed once.
decision = guard(["git", "clean", "-fd"], path="/path/to/lane")
override = guard(
    ["git", "clean", "-fd"],
    path="/path/to/lane",
    lane="lane-id",
    override=issue_override_token(
        authorization=operator_authorization_callback,
        audit_context={
            "actor": "operator",
            "lane": "lane-id",
            "repository": "/path/to/lane",
            "argv": "git clean -fd",
            "operation": "git clean -fd",
        },
    ),
)

Override context is mandatory and exact: the repository is resolved before consumption and argv must be normalized fixed-argument text. Unknown commands, alternate --git-dir/--work-tree targets, aliases/config injection, and non-git executables are refused. The mutation lease is cooperative; a raw external Git process that ignores it remains outside this boundary, and a post-execution tree invariant is returned when such a process races it.

safe_commit stages deletions and untracked files before invoking the configured gate and returns an explicit nothing_left_unstaged assertion plus the staged path set, commit SHA, and refreshed tree baseline evidence. destructive_guard refuses reset --hard, broad checkout/clean/stash operations, forced branch deletion, and force-push by default. A token cannot be minted from configuration or an environment flag; the caller must supply a live authorization callback and actor/lane/repository/argv/operation audit context. An override first creates refs/lane-backup/pre-destructive/<lane>-<uuid> and parks dirty WIP at the lane-private ref; a missing snapshot is a refusal. Use stash_guard.park and stash_guard.unpark for a temporary clean tree instead of the shared refs/stash stack.

flowchart LR
    TREE[Dirty worktree] --> STAGE[git add -A]
    STAGE --> PROVE{nothing left unstaged?}
    PROVE -->|yes| GATE[Configured gate]
    GATE --> RESTAGE[Restage formatter output]
    RESTAGE --> COMMIT[Commit + SHA]
    DANGER[Destructive argv] --> REFUSE[Refuse + safer alternative]
    DANGER -->|single-use override| SNAP[Backup ref + private WIP park]
    SNAP --> EXEC[Execute fixed argv]
Loading
flowchart LR
    V{Worktree has .venv} -->|No| OK[Isolation check passes]
    V -->|Yes| M{Launcher marker config and interpreter are valid}
    M -->|Yes| MANAGED[Managed all-extras environment passes]
    M -->|No| FAIL[Foreign or stale environment blocks finish]
Loading

Canonical manifest gate

For a development bootstrap, the root workspace.yml is the only authority. The Arbora XDG copy retains its canonical bytes, including private runtime values. This package's workspace.yml is a separate portable projection: the workspace root becomes ${REPOSITORY_MANAGER_WORKSPACE_ROOT}, private URL origins become ${ARBORA_REPO_ORIGIN}, and private service suffixes become ${AGENT_UTILITIES_SERVICE_DOMAIN_SUFFIX}. The gate rejects embedded URL credentials, secret fields, and absolute paths outside the declared workspace before either destination can be written.

Validate all three before cloning. --manifest-check never writes and exits 1 when either mirror has drifted. --manifest-sync --manifest-dry-run previews the two updates. A real --manifest-sync stages both files first, replaces them atomically, and rolls back the first replacement if the second one fails.

repository-manager --manifest-check \
  --manifest-source <workspace-root>/workspace.yml \
  --manifest-profile development

repository-manager --manifest-sync --manifest-dry-run \
  --manifest-source <workspace-root>/workspace.yml

Every destination is overridable for an isolated bootstrap or test. The command does not search for a source manifest and never treats the packaged seed as authority. The runtime mirror must match the canonical source byte-for-byte; the portable seed is compared semantically, so seed-only formatting changes do not trigger a replacement. Its JSON result reports only roles, SHA-256 digests, declared profiles/selectors, and selected workspace-relative repository identifiers; it omits local paths.

flowchart LR
    ROOT[Canonical root workspace.yml] --> GATE[repository-manager manifest gate]
    GATE -->|canonical projection / atomic replace| XDG[Arbora runtime copy]
    GATE -->|portable projection / atomic replace| SEED[Packaged distribution seed]
    GATE -->|profile + selector resolution| BOOT[Bounded development bootstrap]
Loading

Profiles make a development subset explicit without changing the repository tree. A profile names one or more selectors. Selectors use stable, workspace-relative identifiers, unambiguous basenames, or *:

profiles:
  development:
    selectors: [core]
selectors:
  core:
    include:
      - agent-packages/scion-sdk
      - agent-packages/agents/repository-manager

The rules are fail-closed:

  • a missing include starts with every repository, while include: [] starts empty;
  • exclude is applied within each selector, then multiple selectors are unioned;
  • * cannot be combined with another value in the same list;
  • every profile reference and every selector member is validated, even when that profile was not requested;
  • a basename that occurs in more than one directory is rejected as ambiguous; use its workspace-relative identifier instead.

A manifest with no requested profile or selector exposes every declared repository. Automation should consume the reported selected_repositories identifiers directly; do not collapse them to basenames when the manifest contains duplicate names.

Default workspace definition

With no -f and no WORKSPACE_YML, DEFAULT_WORKSPACE_YML is the packaged seed repository_manager/workspace.yml; --setup and --clone provision from it. The runtime copy at ~/.config/arbora/workspace.yml is used only by manifest synchronisation (workspace_manifest), never as an implicit override of the packaged default. The seed reads ${REPOSITORY_MANAGER_WORKSPACE_ROOT} (workspace root) and ${ARBORA_REPO_ORIGIN} (private git origin).