repoforge 0.1.12

Safe GitHub and Bitbucket archive discovery, slugging, and fast-forward refresh helpers
Documentation
# RepoForge Agent Guide

RepoForge is a library for local Git archive discovery and safe fast-forward
refresh. It does not call GitHub or Bitbucket APIs, list remote repositories,
manage credentials, clone through a dedicated API, write manifests, or provide
a CLI/dry-run mode. The caller owns those operations and policies.

## Targets And Credentials

- `parse_git_remote_slug` recognizes GitHub (`github.com`) and Bitbucket Cloud
  (`bitbucket.org`) HTTPS, HTTP, SSH, SCP-like SSH, `git+ssh`, and optional-port
  remotes. It returns `(GitProvider, owner/repo)`; Bitbucket's owner is a
  workspace. Deep browser paths and lookalike hosts are rejected.
- Use `parse_github_remote_slug` or `parse_bitbucket_remote_slug` when the
  provider is already known. Use `split_owner_repo_slug` for an explicit
  `owner/repo` target. These functions do not validate remote existence.
- RepoForge reads no token environment variables. A caller's GitHub/Bitbucket
  API client must define its own token contract. Git subprocesses inherit the
  process environment and normal Git credential/SSH configuration.
- Never put tokens in clone URLs, output, errors, or manifests. Prefer credential
  helpers or SSH agents. Do not assume `GITHUB_TOKEN`, `GH_TOKEN`, or a
  Bitbucket token is consumed by RepoForge or automatically used by Git.

```rust
use repoforge::{GitProvider, parse_git_remote_slug, split_owner_repo_slug};

assert_eq!(
    parse_git_remote_slug("git@bitbucket.org:workspace/repo.git"),
    Some((GitProvider::Bitbucket, "workspace/repo".to_string())),
);
assert_eq!(split_owner_repo_slug("octocat/Hello-World"),
           Some(("octocat", "Hello-World")));
```

## Autonomous Workflow

1. Parse and validate the requested provider/slug. Reject unsupported hosts or
   malformed slugs instead of guessing.
2. List remote repositories with a separately configured provider client when
   needed. Bound pagination, retries, concurrency, and result count. RepoForge
   has no remote-list API.
3. Discover local repositories with `discover_git_archives` or
   `discover_git_archives_for_owner`. A missing root returns an empty vector; a
   path that exists but is not a directory returns `OutputNotDirectory`.
4. Build a deterministic plan before mutation: sorted target slug, provider,
   destination, and action (`skip`, `clone`, `refresh`, or `error`). A dry run
   prints this plan and must not call clone or refresh operations.
5. For clone actions, construct a canonical HTTPS URL with
   `git_web_url_for_slug`; never reuse an untrusted raw URL for display. Invoke
   Git through `run_git_capture` or the caller's process layer and check
   `Output::status`. RepoForge has no clone result type.
6. Refresh discovered candidates with `refresh_git_archives` or
   `refresh_one_git_archive`. Refresh checks `git status --porcelain`, refuses
   dirty worktrees, then runs `git pull --ff-only`.
7. Verify successful clone/refresh destinations with `is_git_worktree`,
   `git_remote_origin_url`, `parse_git_remote_slug`, and `git_head`. These are
   structural checks, not content-integrity or signature verification.
8. Write caller-owned manifests only after verification. Record non-secret,
   stable data such as provider, slug, canonical web URL, destination, HEAD,
   action, and outcome. Use an atomic replace and do not claim failed work as
   successful. RepoForge never reads or writes manifests.

## Planning, Existing Paths, And Limits

- Discovery is recursive, sorted, and stops below each worktree. It skips
  `.git`, `.thesa`, and `target`; filters match slug or path case-insensitively.
- Implement skip-existing in the caller. If the planned destination is an
  existing worktree for the same parsed provider/slug, report `skip` (or plan a
  separate refresh when explicitly requested). Never clone over it.
- Treat an existing non-worktree, unreadable remote, or mismatched remote as a
  conflict, not as skippable success. Do not delete, reset, or repurpose it.
- `refresh_git_archives(&archives, concurrency)` uses batches of at most
  `concurrency.max(1)` threads. Choose a conservative positive bound. Bound
  provider listing and clone concurrency separately; RepoForge does not do so.
- Git output is captured, not streamed. Emit concise per-repository outcomes
  and aggregate counts; cap displayed failures/output while preserving the full
  structured result or manifest privately. Never print credential-bearing URLs.

```rust,no_run
use std::path::Path;
use repoforge::{discover_git_archives, refresh_git_archives};

let root = Path::new("./archives");
let archives = discover_git_archives(root, None)?;

// Dry run: report the sorted `archives` plan and stop here.
let summary = refresh_git_archives(&archives, 4);
println!("updated={} unchanged={} failed={}",
         summary.updated, summary.unchanged, summary.failed.len());
# Ok::<(), repoforge::RepoForgeError>(())
```

One possible caller-owned clone step, after planning and destination checks:

```rust,no_run
use std::path::Path;
use repoforge::{GitProvider, git_web_url_for_slug, run_git_capture};

let root = Path::new("./archives");
let destination = "octocat/Hello-World";
let url = git_web_url_for_slug(GitProvider::GitHub, destination)
    .expect("validated owner/repo slug");
let output = run_git_capture(root, &["clone", "--", &url, destination])?;
if !output.status.success() {
    // Convert captured, redacted stderr/status into the caller's failure type.
}
# Ok::<(), repoforge::RepoForgeError>(())
```

## Errors And Safety

- APIs returning `repoforge::Result<T>` can report `OutputNotDirectory`,
  `MissingGit`, `DirtyWorktree`, `GitCommandFailed`, or `Io`.
- `refresh_git_archives` does not return `Result`; inspect every
  `GitRefreshSummary`, especially `failed`, `has_failures()`, and `is_success()`.
  Use `refresh_one_git_archive` when the typed error is required.
- `git_remote_origin_url` returns `None` when Git cannot read the origin; it
  does not distinguish absence from command failure.
- `git_head` returns `Ok(None)` for an unreadable/unborn HEAD.
- RepoForge never deletes, resets, checks out, rebases, merges, stashes, or
  force-pulls. Preserve this contract in autonomous callers and require explicit
  operator policy for any broader mutation.

The same text is available at runtime as `repoforge::AGENT_GUIDE`.