# repoman

**A safety harness for AI-assisted development.** Per-project Incus containers + opinionated NFS backup. Current version: **v0.4.4**.

`repoman` runs each of your projects inside its own [Incus](https://linuxcontainers.org/incus/) (LXC) container with the repo bind-mounted in, so AI coding agents can work with broad permissions inside the container while your host OS, your other projects, and the source repo on disk stay protected. See [BLURB.md](BLURB.md) for the marketing pitch and [VISION.md](VISION.md) for the design rationale.

## Why

AI coding agents are productive *and* prone to "fixing" things you didn't ask them to touch. `repoman` makes the blast radius of any mistake equal to **one container**:

- **Host OS sealed off** — the agent can mangle the container's filesystem; the host's `/etc`, `/usr`, kernel modules, and services are out of reach.
- **Projects isolated from each other** — each project is its own container; an agent in project A cannot see project B.
- **Source code preserved** — your repo lives on the host (`~/repos/<name>`) and is exposed to the container via a uid-shifted bind mount. `repoman remove` deletes the container, never the repo. `repoman sync` mirrors all repos to NFS for belt-and-suspenders recovery.
- **Authenticated agent state shared safely** — vendor profiles let you bind-mount things like `~/.claude` into every container so you don't re-authenticate per project, without exposing SSH keys or shell config.

## Build

```bash
reefc build
```

Produces `./build/repoman`.

## Test

```bash
make test
```

Or per-suite:

```bash
for t in tests/test_*.reef; do
    echo "== $t =="
    reefc run "$t" || exit 1
done
```

## Install

```bash
make
sudo make install        # installs to /usr/local/bin/repoman
                         # vendor profiles to /usr/local/share/repoman/profiles/
```

## Quickstart

```bash
repoman setup                       # one-time host bootstrap (idempotent)
repoman profile install --all       # install vendor profiles
repoman new my-project              # bind ~/repos/my-project into a new container
repoman shell my-project            # drop into it
repoman sync                        # rsync all repos to NFS
repoman remove my-project           # destroy container; repo on host is untouched
```

## Subcommands

| Subcommand | Description |
|---|---|
| `setup [--non-interactive]` | First-time host bootstrap: create Incus project `repoman`, detect host LAN IP, write initial registry. Idempotent. |
| `profile {list,install,diff,remove,show}` | Manage Incus profiles from the vendor library (`/usr/local/share/repoman/profiles/`) and user overrides (`~/.config/repoman/profiles.d/`). |
| `new <name> [--repo <dirname>] [--image <image>]` | Launch a container in the `repoman` Incus project and bind-mount `~/repos/<dirname>` with `shift=true`. Validates profiles are installed before creating. |
| `list` | Print a table of all registered projects (read-only). |
| `status [name]` | Show project state from registry + live incus query. |
| `shell <name> [--cwd <path>]` | Open a bash login shell inside the project's container, in the repo's directory by default. |
| `sync [name] [--no-delete] [--dry-run]` | Mirror local repos to NFS backup via rsync. NFS auto-mount aware. |
| `remove <name> [--yes | -y] [--keep-incus]` | Delete the incus container and registry entry. Prompts for confirmation unless `--yes`. The source repo on host is **never** deleted. |
| `--help` / `-h` / `help` | Show usage and subcommand list. |
| `--version` / `-V` | Print version string. |

## Profile library

`repoman` ships a vendor profile library at `/usr/local/share/repoman/profiles/`:

- **`claude-share`** — bind `~/.claude` and `~/.local/bin/claude` so containers share the host's Claude CLI auth, history, and plugins. One `claude login` covers every project.
- **`llm-share`** — bind `/usr/local/bin/ollama` and `~/.ollama`, set `OLLAMA_HOST=http://<host-lan-ip>:11434` so containers reach the host's ollama daemon over LAN.
- **`dotfiles`** — bind `~/.gitconfig` and `~/.hgrc` (extend with your own).

Manage with `repoman profile`:

```bash
repoman profile list
repoman profile install <name>
repoman profile install --all
repoman profile diff <name>            # show drift between file and incus state
repoman profile show <name>            # print rendered YAML
repoman profile remove <name>          # remove from incus (file untouched)
```

### Customizing profiles

To override a vendor profile, drop a copy into your user dir and edit:

```bash
cp /usr/local/share/repoman/profiles/dotfiles.yml ~/.config/repoman/profiles.d/
$EDITOR ~/.config/repoman/profiles.d/dotfiles.yml
repoman profile install dotfiles       # applies the user version (shadows vendor)
```

User profiles in `~/.config/repoman/profiles.d/` always win over vendor profiles of the same name. `repoman profile list` shows the active source for each.

## Output and logging

By default `repoman` runs quietly — only its `==> ...` markers and any errors reach the terminal. Subprocess probes (e.g. `incus project show`, `findmnt`) have their output suppressed. The actual user-facing operations (container launch, rsync) pass their stdio through.

Run with `--verbose` (`-v`) to see suppressed probe output for troubleshooting. Pass `--quiet` (`-q`) to force quiet if your config defaults to verbose.

Every invocation writes a log file at `<logdir>/<project>-<verb>.log`, truncated each run. So if `repoman new veemarker` fails, the log lives at `~/.local/state/repoman/veemarker-new.log` until the next run with the same name+verb. Whole-tree sync uses `all-sync.log`.

## Configuration

Central registry: `~/.config/repoman/repoman.toml` (managed; do not edit while `repoman` is running).

Tool-level settings under `[repoman]`:

| Field | Values | Default | Effect |
|---|---|---|---|
| `output` | `"quiet"` \| `"verbose"` | `"quiet"` | Default output mode (CLI flags override). |

Host facts under `[host]` (schema 3, added in v0.4):

| Field | Default | Effect |
|---|---|---|
| `lan_ip` | (auto-detected from `br0`) | Substituted into profiles as `${HOST_LAN_IP}` — used by `llm-share` to point containers at the host's ollama daemon. |

Defaults under `[defaults]`:

| Field | Default | Effect |
|---|---|---|
| `repos_root` | `~/repos` | Local NVMe canonical location for working copies. |
| `backup_root` | `/nfs/repos` | NFS-mounted backup destination. |
| `logdir` | `~/.local/state/repoman` | Per-invocation log files. |
| `incus_project` | `repoman` | Incus project containers are created in. |
| `default_image` | `images:ubuntu/26.04/cloud` | Image used when no `--image` flag and no override. |
| `profiles` | `["default", "claude-share"]` | Profiles applied when creating containers. |

Per-project overrides: `~/.config/repoman/repos.d/<container-name>.toml`:

```toml
[container]
image    = "images:debian/12/cloud"
profiles = ["default", "claude-share", "node-dev"]

[[mount]]
source = "~/.npm"
path   = "/home/ctusa/.npm"

[env]
NODE_ENV = "development"
```

## Migrating from v0.3

If you ran `repoman setup --with-llm` on v0.3, your registry is at schema 2 and includes a `[defaults.llm]` block. v0.4 reads that block on first load, extracts the ollama URL's host portion into `[host].lan_ip`, and writes the registry as schema 3 on the next save. The migration is automatic and lossless for the one fact that's still useful.

The `--hermes` / `--no-hermes` / `--purge-hermes` flags were reverted in v0.3 before tagging — they never shipped. Agents that need per-container installs (hermes and similar) are the user's responsibility: `repoman shell <name>` and run the installer yourself.

## Smoke test (requires Incus + NFS)

```bash
# In an existing repo dir under ~/repos:
repoman new test-foo
repoman sync test-foo --dry-run
repoman remove test-foo
```

## License

Common Development and Distribution License (CDDL) Version 1.0. (C)opyright 2026, Leafscale, LLC. See the [LICENSE](LICENSE) file for full terms.
