repoforge 0.1.4

Safe repository archive discovery, GitHub slugging, and fast-forward refresh helpers
Documentation
# RepoForge

RepoForge provides repository archive discovery and safe refresh helpers used by
`thesa`.

Made by Trevor Knott for Knott Dynamics. RepoForge is part of the thesa archive
stack alongside `modelforge`, `siteforge`, Scrin, and Aisling.

The crate is intentionally focused. It scans an archive root for Git worktrees,
derives stable `owner/repo` slugs from GitHub remotes or archive paths, and
refreshes clones with `git pull --ff-only` without touching dirty worktrees.

RepoForge is a library crate, not a full CLI. It gives applications the safe Git
operations and deterministic archive identifiers they need while leaving UI,
manifest writing, and policy decisions to the caller.

## What It Does

- Finds Git worktrees recursively under an archive root.
- Converts GitHub remotes into stable `owner/repo` slugs.
- Falls back to archive path slugs when remotes are missing or non-GitHub.
- Restricts discovery to one GitHub owner/user/org when requested.
- Filters discovered archives by slug or path text.
- Refuses dirty worktrees before refresh.
- Runs `git pull --ff-only` for clean worktrees.
- Returns structured updated, unchanged, and failed outcomes.

## What It Does Not Do

- Does not delete repositories.
- Does not reset, checkout, rebase, merge, stash, or force-pull.
- Does not write archive manifests or checksums.
- Does not assume a terminal UI.
- Does not require GitHub API credentials.

## Install

```toml
[dependencies]
repoforge = "0.1"
```

The package and library crate name are both `repoforge`.

## Archive Layout

RepoForge is designed for archive trees like this:

```text
archives/
  octocat/
    Hello-World/
      .git/
  rust-lang/
    rust/
      .git/
```

When `remote.origin.url` is a GitHub URL, the slug is derived from the remote.
For example `https://github.com/octocat/Hello-World.git` becomes
`octocat/Hello-World`.

If there is no GitHub remote, RepoForge falls back to the path layout. For
example `archives/octocat/Hello-World` becomes `octocat/Hello-World`.

## Discovery

```rust
use std::path::Path;

let archives = repoforge::discover_git_archives(Path::new("./archives"), None)?;
for archive in archives {
    println!("{} -> {}", archive.slug, archive.path.display());
}
# Ok::<(), repoforge::RepoForgeError>(())
```

Use a filter to limit results by slug or path:

```rust
let archives = repoforge::discover_git_archives(
    std::path::Path::new("./archives"),
    Some("octocat"),
)?;
# Ok::<(), repoforge::RepoForgeError>(())
```

Discovery is recursive but stops descending when it finds a Git worktree.
`.git`, `.thesa`, and `target` directories are skipped.

Discover only one GitHub owner/user/org:

```rust
let archives = repoforge::discover_git_archives_for_owner(
    std::path::Path::new("./archives"),
    "octocat",
    None,
)?;

assert!(archives.iter().all(|archive| archive.matches_owner("octocat")));
# Ok::<(), repoforge::RepoForgeError>(())
```

Owner matching is case-insensitive and uses the derived `owner/repo` slug. This
works for GitHub remote-derived slugs and archive layouts like
`archives/<owner>/<repo>`.

## API Surface

- `discover_git_archives(root, filter)`: find archive worktrees.
- `discover_git_archives_for_owner(root, owner, filter)`: find worktrees for one GitHub owner/user/org.
- `refresh_git_archives(archives, concurrency)`: refresh many worktrees.
- `refresh_one_git_archive(archive)`: refresh one worktree.
- `parse_github_remote_slug(remote_url)`: parse GitHub remotes into `owner/repo`.
- `fallback_archive_slug(root, repo_path)`: derive a slug from archive layout.
- `ensure_clean_git_worktree(archive)`: fail if local changes exist.
- `git_head(archive)`: read the current `HEAD` hash.
- `run_git_capture(repo_path, args)`: run low-level Git commands with RepoForge errors.

Core data types:

- `GitArchiveCandidate`: discovered worktree path, slug, and optional remote.
- `GitArchiveCandidate::owner()`: borrow the slug owner.
- `GitArchiveCandidate::repo_name()`: borrow the slug repository name.
- `GitArchiveCandidate::matches_owner(owner)`: case-insensitive owner match.
- `GitRefreshSummary`: aggregate counts, successful entries, and failures.
- `GitRefreshStatus`: `Updated` or `Unchanged`.
- `RepoForgeError`: typed errors for non-directory roots, missing Git, dirty trees, Git command failures, and I/O.

## Refresh

```rust,no_run
let archives = repoforge::discover_git_archives(std::path::Path::new("./archives"), None)?;
let summary = repoforge::refresh_git_archives(&archives, 4);

println!(
    "updated={} unchanged={} failed={}",
    summary.updated,
    summary.unchanged,
    summary.failed.len()
);

if summary.has_failures() {
    for failure in &summary.failed {
        eprintln!("{}: {}", failure.slug, failure.message);
    }
}
# Ok::<(), repoforge::RepoForgeError>(())
```

Refresh behavior:

- Runs `git status --porcelain` before pulling.
- Dirty worktrees fail fast and are not pulled.
- Runs `git pull --ff-only` for clean worktrees.
- Reports `Updated` when `HEAD` changes.
- Reports `Unchanged` when the pull succeeds without changing `HEAD`.
- Uses bounded concurrency with `refresh_git_archives`.

## Slug Helpers

```rust
assert_eq!(
    repoforge::parse_github_remote_slug("git@github.com:octocat/Hello-World.git"),
    Some("octocat/Hello-World".to_string())
);
```

Supported GitHub remote forms include HTTPS and SSH:

- `https://github.com/owner/repo.git`
- `http://github.com/owner/repo.git`
- `git@github.com:owner/repo.git`
- `ssh://git@github.com/owner/repo.git`

## Safety Contract

RepoForge does not delete, reset, checkout, stash, or force-pull anything. It
only uses read-only Git inspection commands plus `git pull --ff-only` after
confirming the worktree is clean.

Callers such as `thesa` remain responsible for archive metadata sidecars,
checksums, and higher-level reporting.

## Package Contents

Cargo releases are explicitly whitelisted to avoid shipping sessions, notes,
build output, or local archives. The package contains only:

- `Cargo.toml`
- `Cargo.lock`
- `README.md`
- `LICENSE`
- `CHANGELOG.md`
- `src/**`

## Role In Thesa

`thesa` uses RepoForge for Git archive discovery, GitHub slugging, dirty-tree
protection, and fast-forward-only refreshes. `thesa` then wraps those outcomes
with Scrin/Aisling terminal reporting and `.thesa` integrity manifests.