# repoman — Vision Doc

**Status:** v0.4.4 shipped 2026-05-11
**Origin:** Bash prototype at `~/.local/bin/repoman` (~200 LOC), April 2026. Productized rewrite in **reef-lang** began v0.1 (2026-04-29); current release is v0.4.4.

---

## Vision sentence

`repoman` is a safety harness for AI-assisted development: it isolates each project inside its own Incus (LXC) container, bind-mounts the repo into it, and mirrors everything to NFS — so AI coding agents can work with broad permissions inside the container while the host OS, sibling projects, and the source repository on disk stay protected.

## Why this exists

AI coding agents — Claude Code, Aider, Cline, Continue, Gemini CLI, and the wave of others — are dramatically more productive when granted broad permissions. They are also dramatically more capable of "fixing" things you did not ask them to touch: editing the wrong file tree, running install scripts against the host, mass-replacing across a repo, deleting paths they misread, pushing stray commits.

The original problem (which seeded the bash prototype): run Claude Code with `--dangerously-skip-permissions` without taking the host down with it. The solution: **per-project Incus containers + bind-mounted repos + NFS backup + ZFS snapshots on the file server**. Doing it manually with raw `incus` and `rsync` works but accretes ad-hoc shell scripts. `repoman` makes the workflow first-class and turns the safety properties into invariants of the tool, not habits of the operator.

### What "safety harness" actually means

1. **Host OS sealed off.** Containers run with their own root filesystem, init, package manager, services. An agent can `apt install`, mangle `/etc`, scribble into `/usr`, install systemd units — none of it crosses the boundary. Worst case is "throw away the container."
2. **Projects isolated from each other.** Every project gets its own container in the `repoman` Incus project namespace. An agent in container A cannot read or write container B's files. No accidental cross-edits between sibling repos, no shared `node_modules` corruption, no rogue file watchers.
3. **Source code preserved.** The repo lives on the host at `~/repos/<name>` and is exposed to the container via a uid-shifted bind mount (`shift=true`, since v0.4.2). `repoman remove` deletes the container — **never the repo**. The confirmation prompt spells out exactly what gets removed (v0.4.3).
4. **Backup as recoverability.** `repoman sync` rsyncs every repo to NFS. Run that NFS on ZFS and snapshots give you point-in-time history. If an agent does mangle the working tree, you have last night's copy.
5. **Authenticated agent state shared safely.** The vendor profile library lets you bind-mount things like `~/.claude` (auth + history + plugins) into every container — one login, every project — without exposing SSH keys, browser cookies, or shell config.

## Audience

Homelab developer running:
- **Incus** for containerization (Linux-only).
- **Local SSD/NVMe** for active project working copies (latency matters for the agent's iteration loop).
- **NFS-mounted remote storage** for durable backup (ideally ZFS-backed, for snapshot history).
- **One or more AI coding agents** (Claude Code, etc.) that they want to grant generous permissions to.

One developer, multiple projects. Not multi-user, not multi-host coordination.

Realistic audience size: small. Niche tool serving its narrow audience well > generic tool nobody loves.

## Mission (re-anchored in v0.4)

`repoman` has exactly four jobs:

1. **Provision** per-project Incus containers that bind-mount the user's git/hg repos (`repoman new`).
2. **Backup** repos to NFS via rsync (`repoman sync`).
3. **Share** host-side resources (configs, agents, runtimes) into containers via Incus profiles (`repoman profile`).
4. **Operate** the above with quality-of-life subcommands (`list`, `status`, `remove`, `shell`, `setup`).

**What `repoman` is NOT:**
- A host-configuration tool. It does not install or configure host services (ollama, hermes, kernel modules).
- A container-image builder. It uses upstream Incus images; image curation is somebody else's tool's job.
- An arbitrary install-script runner inside containers. Per-container provisioning is the user's shell script in their project repo.

The v0.3 attempt to bind-mount hermes from the host failed because hermes pins to a uv-vendored host-only Python. The v0.4 brainstorm explored image management to fix that, then rejected it as ongoing maintenance overhead. The conclusion: **agents fall into two categories** — bind-mountable (claude works) and non-shareable (hermes needs per-container install). `repoman` does the bind-mountable case via profiles; the rest is shell-script territory in the user's project repo.

## Differentiators (vs rolling your own scripts)

1. **Incus `project` for ownership.** All `repoman`-managed containers live in the `incus project create repoman` namespace — clean scoping, no leakage into the user's other Incus work, easy to nuke the entire estate.
2. **Single tool covering container lifecycle + project conventions + backup + profile management.** Existing tools each cover one slice (`incus` for containers, `restic`/`borg` for backup, `distrobox` for dev shells); none do all four with conventions tuned for "AI-agent-in-a-Linux-VPS on a homelab."
3. **Opinionated backup story.** rsync mirror + autofs-aware mount checks + ZFS snapshots assumed on the file server. No bespoke history layer — ZFS is the time machine.
4. **Profile library with vendor/user split.** Shareable host-side things (auth state, runtime binaries, dotfiles) are first-class via Incus profiles. Vendor ships sensible defaults; user shadows them in `~/.config/repoman/profiles.d/`. `${HOST_LAN_IP}`, `${USER}`, `${HOME}` substituted at install time.
5. **Both interactive and flag-driven.** Same code path serves a guided wizard and a shell-scriptable CLI.
6. **Native single-binary distribution** via reef compiling to native code. No runtime install for users.

## Subcommands as shipped (v0.4.4)

| Subcommand | Purpose |
|---|---|
| `repoman setup [--non-interactive]` | First-time host bootstrap. Creates Incus project `repoman`, detects host LAN IP, writes initial registry, ensures `~/.config/repoman/profiles.d/` exists. Idempotent. |
| `repoman profile {list,install,diff,remove,show}` | Manage Incus profiles from `/usr/local/share/repoman/profiles/` (vendor) and `~/.config/repoman/profiles.d/` (user shadow). Renders `${HOST_LAN_IP}` / `${USER}` / `${HOME}` at install time. |
| `repoman new <name> [--repo <dirname>] [--image <img>]` | Create a managed container. Pre-validates profiles are installed, bind-mounts the repo with `shift=true`, applies profiles, restarts. Interactive form prompts for unset values. |
| `repoman list` | Print a table of managed containers: name, repo path, image, created, last sync, backup flag. Reads from registry. |
| `repoman shell <name> [--cwd <path>]` | Drop into the container as the local user, in the repo's directory. Replaces the verbose `incus shell --user 1000 --cwd ...` invocation. |
| `repoman sync [name] [--no-delete] [--dry-run]` | rsync mirror local → NFS, all projects or one. NFS auto-mount aware. ZFS snapshots on the file server provide history. |
| `repoman remove <name> [--yes \| -y] [--keep-incus]` | Destroy the container; the repo on host is never touched. Prompts for confirmation by default; `--yes` skips the prompt. |
| `repoman status [name]` | Health check: container state, mounts, NFS reachability, last sync. |

## Out of scope

Brainstormed and rejected during the v0.4 design conversation (see [memory: project_repoman.md](#) for full rationale):

- **Other container runtimes** (Docker, Podman). Incus only — we depend on its full-Linux-VPS shape.
- **Other backup destinations** (S3, restic, borg). NFS only.
- **macOS/Windows.** Linux only.
- **Multi-host coordination, replication.**
- **ZFS snapshot management** (file-server-side; user runs sanoid/syncoid themselves).
- **Cron installation** (provide a doc snippet, not a subcommand).
- **IDE integration.**
- **Image building / curation** (`repoman image build/refresh/list/prune`). Rejected: maintenance burden outweighs benefit for homelab usage volumes.
- **`[install]` block in override files** (per-project install commands run in container at create). Rejected: users put `scripts/install.sh` in their project repo and run it after `repoman shell`. Not worth a TOML schema + lifecycle + security model.
- **Setup wizard configuring host services** (ollama auto-install, systemd overrides, model pulls). Rejected: scope creep across the host-config boundary.
- **`--hermes` / per-container hermes provisioning.** Hard-rejected after v0.3 iterations failed on uv-vendored Python portability.

## Configuration

**Central registry:** `~/.config/repoman/repoman.toml` (schema 3 as of v0.4)

```toml
schema = 3

[repoman]
output = "quiet"

[host]
lan_ip = "192.168.1.42"

[defaults]
repos_root     = "~/repos"
backup_root    = "/nfs/repos"
logdir         = "~/.local/state/repoman"
incus_project  = "repoman"
default_image  = "images:ubuntu/26.04/cloud"
profiles       = ["default", "claude-share"]

[[project]]
name        = "isurus"
repo        = "isurus-project"
image       = "images:ubuntu/26.04/cloud"
profiles    = ["default", "claude-share"]
created     = "2026-04-28T15:00:00Z"
last_sync   = "2026-04-28T20:17:03Z"
backup      = true
```

**Per-project override (optional):** `~/.config/repoman/repos.d/<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"
```

## Implementation: reef-lang

Reef has the right shape for this:

- **Native compilation** → C → GCC/Clang → static binary. Single-file distribution.
- **Stdlib coverage in use:**
  - `encoding.toml` — central registry + per-project overrides + profile YAML helpers
  - `sys.args` / `sys.flag` — CLI parsing
  - `sys.env` — env var reads
  - `sys.process` — argv-list `process_run` / `process_spawn` for rsync/incus shell-outs (no `process_spawn_shell`: user-derived names cannot trigger shell injection)
  - `io.console` — interactive prompts (`confirm`, `confirm_default_no`)
  - `io.file`, `io.dir`, `io.path`, `io.stream` — local FS ops
  - `core.result_generic`, `core.option` — error handling primitives
- **Multi-platform reef** = future macOS/BSD/illumos support comes for free if/when needed.
- **Currently pins reef-lang 0.5.20** (source at `~/reef-lang-0.5.20-source/`).

### Resolved implementation questions

- **Subprocess execution.** `sys.process` provides the POSIX surface needed. Rule: argv-list `process_run` / `process_spawn` exclusively; never `process_spawn_shell` for user-derived inputs.
- **Incus integration.** Decision: CLI shell-out (`incus` invoked with argv-list, `--format json` where useful). REST-via-unix-socket migration deferred until a concrete need arises.
- **Interactive UX.** Line-based prompts using `io.console`. Rich TUI was out of scope and stayed out.
- **uid mapping for bind mounts.** Resolved in v0.4.2: every `cmd_new` repo bind is created with `shift=true` so host uid 1000 maps to container uid 1000.

## Versions shipped

- **v0.1** (2026-04-29): `new` + `sync` subcommands, Incus `project repoman` namespace, central registry.
- **v0.2** (2026-05-05): added `list`, `status`, `remove`, `shell`.
- **v0.3** (2026-05-08): `setup` wizard, `llm-share` profile (ollama-over-LAN), schema-2 migration. `--hermes` experiment reverted before tag.
- **v0.4** (2026-05-09): `profile` subcommand family, vendor profile library with user-shadow override, schema-3 migration (drops `[defaults].llm`, adds `[host].lan_ip`), setup wizard trimmed (no more `--with-llm`).
- **v0.4.2** (2026-05-11): `cmd_new` bind-mounts with `shift=true` so host uid maps into the container — agents in containers can actually write to the repo as the in-container user.
- **v0.4.3** (2026-05-11): `cmd_remove` confirmation prompt spells out exactly what gets removed; reassures the user that the source repo on host is not touched.
- **v0.4.4** (2026-05-11): `--version` reports the actual version; build-warning cleanup.

## Success criteria for v0.x

- [x] Builds clean from a fresh reef-lang install on Linux x86_64.
- [x] `repoman setup` runs idempotently and produces a working baseline on a fresh host.
- [x] `repoman new` creates a container, matches the bash prototype's behavior, and persists in the registry with per-project override support.
- [x] `repoman sync` matches the bash prototype's behavior with autofs check + mirror semantics.
- [x] All subcommands have both interactive and flag-driven invocation paths.
- [x] Bind mounts respect host UID/GID so containers can write to the repo.
- [x] License chosen (CDDL 1.0, Leafscale, LLC as steward/Initial Developer).
- [ ] Single static binary distributable via tarball or per-distro package.
- [x] README + VISION + per-feature doc.
- [ ] Public repo on a forge with a contribution guide.

## Open product questions

1. **Forge.** GitHub, Codeberg, self-hosted? Distribution implications (binaries, releases).
2. **Versioning toward 1.0.** Current cadence is rapid patch releases. What's the 1.0 bar? Probably "stable schema + public forge + tarball releases."
3. **Logging / observability.** Stderr + per-invocation log files today. Structured (JSONL) output mode for automation — needed yet?
4. **Configuration migration policy.** Schema 1 → 2 → 3 worked as implicit lossless migration on parse. Same approach for the next bump?

---

## Naming convention

- Container name = `<name>` (positional, required).
- Repo dirname = `<name>` by default, overridable with `--repo <dirname>`.
- Repo absolute path = `<repos_root>/<repo-dirname>`.
- No magic suffix logic. Explicit > implicit.

## Differentiation reality check

**Why someone would pick `repoman`:**
- They want to run AI agents with `--dangerously-skip-permissions` (or its equivalent) without taking the host down.
- They want the Incus-project-as-namespace pattern but don't want to wire it themselves.
- They want a guided onboarding into "this style of homelab dev workflow."
- They want a unified registry of "what projects do I have, where are they, when did they last back up."

**Why they wouldn't:**
- They already have ansible/nix/etc. covering this.
- They use Docker, not Incus.
- They don't separate local working copies from backup destinations.
- They run their agents under tighter constraints (microVMs, gVisor, restricted unix users) and don't need a container-shaped harness.

That's fine. The audience is small and that's the intentional design.
