Files
GitManager/AGENT.md
T
TBNilles e3336efc25 Correct Claude Desktop connection: local stdio bridge, not GUI connector
The GUI custom-connector flow validates/calls from Anthropic's cloud and can't reach a localhost server, so a 127.0.0.1 URL fails there. The working path is a local mcp-remote stdio bridge configured in claude_desktop_config.json pointing at http://127.0.0.1:8080/mcp. Updated AGENT.md 8.1/11 and CHANGELOG. HTTPS on :8443 stays available but optional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-20 07:52:31 -04:00

548 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AGENT.md
> **This file is the contract for Claude Code working on this repository.**
> Read it fully before any task. It defines the architecture, the nonnegotiable
> rules, and the selfdocumenting workflow you must follow on every change.
>
> When this file and the code disagree, this file wins until you are explicitly
> told to change the file. If a request conflicts with a rule here, **pause and
> ask** before proceeding.
---
## 0. What this project is
A standalone, longlived development application: a **multirepo web dashboard for
Git**. It scans one or more configured roots on the host, discovers every Git
repository under them, and presents a control panel over the whole set — perrepo
status (clean/dirty, ahead/behind, current branch), branches, remotes, recent
commits, stashes, and tags — plus userinitiated Git actions (fetch, pull, push,
checkout, create branch, commit, view diffs, manage remotes). It has peruser
dockable UI panels and is architected so that host/forge integrations
(GitHub/GitLab pull and mergerequests) can be attached without touching the
core.
**The larger purpose — a twoway companion to Claude.** The dashboard is the
visible half. The app is also built to work *with* Claude (used from Claude
Desktop / Claude Code) in both directions:
- **Claude → app:** the app is an **MCP server** Claude can drive — list/switch
repos, run Git operations, and create/merge/cleanup pull requests — so Claude
can manage repositories on the user's behalf.
- **App → Claude:** when the user does something in the app (switches project,
merges a PR), the app makes that known so Claude stays in sync. The headline
case is a **graceful project handoff**: the user switches project in the app,
Claude finishes to a safe stopping point, switches, and the user is notified
(Section 8).
The intended user is a developer who does **not** want to memorize Git commands:
the GUI offers plainlanguage, rightclick commands (Section 6), and Claude can
run the same operations through the MCP surface. This is why the app exists — a
plain dashboard is only step one.
This is a **dev environment first**. It is expected to grow continuously via
incremental requests ("add X functionality"). Every addition must keep the
selfdocumenting workflow in Section 9 intact.
**Trust model:** there is **no user authentication**. The app is a singleoperator
tool that acts as the host user against local repositories. It binds to
**localhost** by default; exposing it to a network is the operator's decision and
their responsibility. Do not add a login/auth layer without asking.
---
## 1. Nonnegotiable architectural laws
These are hard rules. Do not violate them, do not "optimize them away," and do
not assume an exception without asking.
### 1.1 Web Components are the only UI unit (ActiveXspirit, enforced)
The UI is built **exclusively** from native Web Components. The design intent is
deliberately modeled on the *selfcontained control* philosophy of Windows98era
ActiveX/OCX controls — **the spirit, not the dead COM/binary/registry mechanism**.
Concretely, **every** UI element that does real work MUST be:
- A **Custom Element v1** (`customElements.define('x-thing', ...)`).
- Encapsulated with **Shadow DOM** (`attachShadow({mode:'open'})`). Styles and
DOM are isolated. No leaking global CSS into components, no reaching out of a
component into another component's internals.
- **Selfinstantiating**: the page declares the element in markup (or via a thin
loader). The browser "constructs" it. The page does not micromanage it.
- **Independently live**: on connect, each component **fetches its own data and
renders itself**. Components do **not** wait for a central controller to hand
them data. One component being slow or failing must not block another.
- **Selfcontained**: no shared mutable global state. Crosscomponent
communication happens only via (a) DOM custom events (`dispatchEvent` of a
`CustomEvent` that bubbles/composes) or (b) explicit attributes/properties set
on the element. Treat each component like a control you could drop onto any
page and it would just work.
- **Lifecyclecorrect**: implement `connectedCallback` / `disconnectedCallback`
and clean up timers, listeners, and inflight fetches on disconnect.
If you are tempted to introduce a heavy SPA framework (React/Vue/Angular/Svelte)
for the component layer — **stop and ask.** The default answer is no. Vanilla
Web Components are the chosen model. (Small, dependencyfree helpers like
`lit` *may* be proposed, but only with explicit approval, because `lit` still
produces standardsbased custom elements.)
**Shared styling — design tokens.** The one sanctioned crossshadowboundary
styling channel is **CSS custom properties**. The shared theme palette and corner
radii live as tokens (`--color-*`, `--surface-*`, `--border-*`, `--fill-*`,
`--radius*`) in `web/static/app.css :root`; custom properties inherit *into* every
shadow root, so a component references them with `var(--…)` for all shared colors
and radii instead of hardcoding hex. This is **not** the forbidden "leaking global
CSS" — no global *selectors* reach into a component; it is the deliberate theming
layer (change a token once, the whole UI follows). Status feedback is semantic —
destructive/error use the `--color-danger*` set, success uses `--color-success`,
and Gitstate colors (ahead/behind, clean/dirty, conflicted) get their own
semantic tokens. **New or edited components MUST consume the tokens for shared
values** rather than reintroducing hardcoded hex.
### 1.2 Component foldering & percomponent docs
- There is a **toplevel `components/` folder**. All web components live there.
- **Each component gets its own folder**: `components/<component-name>/`.
- **Each component folder contains its own `<component-name>.md`** (see Section 9)
that records the **history and intent** of that component and acts as the
scoped context when working on it. Componentspecific detail belongs in that
file, **not** in this AGENT.md. This keeps AGENT.md lean as the app grows.
### 1.3 Git is the system of record (the app never silently mutates a repo)
- **The Git repositories are the truth.** The app holds only a **derived,
readoptimized inmemory index** of repo state (discovered repos, their status,
branches, remotes, recent log), refreshed by the scanner (Section 5). That index
is a cache — it can be thrown away and rebuilt from the repos at any time.
- **All mutations go through explicit, userinitiated Git operations.** The app
**never** writes to a repository except in direct response to a user action
routed through the `internal/git` boundary. No background process ever mutates a
working tree, index, or ref.
- **The background scanner is readonly.** It may run `status`, `rev-list`,
`for-each-ref`, `log`, and (only when explicitly enabled) `fetch`. It must never
run a command that changes local state.
- **Never invent local persistence for domain state.** If something must survive a
restart and it isn't already in Git, it is either app config (`.env`, Section
1.4) or peruser UI state (browser `localStorage`, Section 4). Adding any other
datastore (SQLite, a serverside DB) requires asking first — the default is no.
### 1.4 Destructive Git operations are explicit, confirmed, and never automatic
Operations that can lose work or rewrite shared history are a distinct class and
must be treated as such:
- **Always require an explicit, deliberate user action** (a dedicated control, not
a side effect of another action) **and a confirmation** that names exactly what
will happen and to which repo/branch.
- This class includes at least: `push --force`/`--force-with-lease`, `reset --hard`,
`checkout`/`restore` that discards uncommitted changes, `clean -fd`, branch/tag
deletion, `stash drop`/`clear`, history rewrites (`rebase`, `commit --amend`),
and remote deletions.
- **Never chain a destructive operation into an automated flow** and never pick a
destructive default. Prefer the safe variant (`--force-with-lease` over
`--force`) and surface it as such.
### 1.5 Configuration via `.env`
Repo scan roots, the `git` binary path, scan/fetch behavior, the listen address,
and any optional forge tokens are all configurable through a single `.env` file. A
committed `.env.example` documents every variable. **Never commit a real `.env`**
and never hardcode paths, tokens, hosts, or the set of watched repositories.
### 1.6 Everything runs in Docker / dockercompose
The dev environment (the Go app + hot reload) is brought up with
`docker compose up`. Because the app operates on repositories that live on the
**host**, the compose file **mounts the configured repo roots into the container**
(readwrite, since Git operations write to them) along with whatever Git needs to
authenticate to remotes (SSH agent socket / mounted keys, or a credential helper).
The app must be runnable by a new developer with: clone → copy `.env.example` to
`.env` → set the repo roots → `docker compose up`. Document any hostside
prerequisites (SSH agent, credential helper) in `.env.example` and the help page.
### 1.7 One service layer behind both the GUI and the MCP server
The web UI and the MCP server (Section 8) are **two front doors to the same
capabilities** — never two implementations. Every operation (a Git action, a
forge action, switching the active project) lives once in an internal service
that goes through the `internal/git` and `internal/forge` boundaries; the Echo
HTTP handlers and the MCP tool handlers are **thin adapters** that call it. A
capability added for the GUI is therefore available to Claude, and vice versa,
and safety rules (§1.4) are enforced in the shared layer so neither front door
can bypass them. Do not implement a Git/forge operation directly in an HTTP or
MCP handler.
---
## 2. Technology stack (locked unless told otherwise)
| Concern | Choice | Notes |
|---|---|---|
| Language | **Go** | Backend, page rendering, Git orchestration, scanner. |
| HTTP framework | **Echo** (`github.com/labstack/echo/v4`) | Idiomatic, fast, good middleware story. If you prefer Chi, ask first. |
| Page rendering | Go serverrendered HTML shell | Server emits the page + declares web components; components fetch their own data. Use `html/template`. |
| Git access | **System `git` via `os/exec`**, wrapped behind an `internal/git` interface | The system binary is authoritative: it honors the user's credential helpers, SSH keys, hooks, and config exactly. PureGo `go-git` may be proposed for cheap readonly queries, but only behind the same interface and only with approval. |
| Repo discovery / index | **Inmemory cache + `github.com/fsnotify/fsnotify`** (optional) | Scan roots for `.git`, hold an index, refresh on interval and/or on filesystem change. |
| Forge integration (PRs/MRs) | **Providerabstracted** (`internal/forge`) — **Gitea/Forgejo first** via `code.gitea.io/sdk/gitea`; GitHub (`github.com/google/go-github`) / GitLab later behind the same interface | **Read/write**, tokengated per host. Writes (merge PR, delete branch) are confirmed per §1.4. See Section 8. The primary host is a selfhosted Gitea (`git.nilles.net`). |
| MCP server | **`github.com/modelcontextprotocol/go-sdk`**, served over **Streamable HTTP** at `/mcp` | Claude Desktop connects as a custom connector. Tools are thin adapters over the shared service layer (§1.7). See Section 8. |
| Realtime (app → browser) | **ServerSent Events** (`net/http`, stdlib) | Push activity + handoff notifications and live repo updates to the components; replaces list polling over time. |
| Diff rendering | Server produces unified diff from `git`; client renders it in a component | No heavy client diff lib without asking. |
| Config | **`.env`** via `github.com/joho/godotenv` + a typed config struct | Section 1.5. |
| Logging | **`log/slog`** → stdout/stderr (structured), optional rotating file sink | See Section 7. No database sink (there is no database). |
| Hot reload (dev) | **air** (`github.com/air-verse/air`) | Inside the app container. |
Anything not in this table that you want to add as a dependency: **propose it and
wait for approval.** Keep the dependency surface small. Note in particular there is
**no database and no auth library** — do not add one without asking (Section 1.3,
Section 0).
---
## 3. Repository layout (target)
```
.
├── AGENT.md # this file — lean, architecture-level only
├── CHANGELOG.md # append-only running history of ALL changes (Section 9)
├── .env.example # every config var, documented, no secrets
├── docker-compose.yml # go app (+ hot reload); mounts host repo roots + git creds
├── Dockerfile # multi-stage build for the Go app (git installed in the image)
├── .air.toml # hot reload config (dev)
├── cmd/
│ └── server/main.go # entrypoint: wire config, git, scanner, router
├── internal/
│ ├── config/ # .env loading, typed config struct
│ ├── git/ # THE Git boundary: interface + os/exec impl (all git ops)
│ ├── repos/ # discovery, in-memory index/cache, refresh scanner worker
│ ├── service/ # the ONE service layer both the HTTP API and MCP call (§1.7)
│ ├── forge/ # provider-abstracted PR/MR read+write (Gitea first) — Section 8
│ ├── mcp/ # MCP server: tool handlers (thin adapters over service) — Section 8
│ ├── activity/ # active-project state, activity feed, pending-switch handoff — Section 8
│ ├── logging/ # slog handler → stdout/stderr (+ optional file)
│ └── render/ # html/template page shell rendering
├── components/ # ALL web components live here (Section 1.2)
│ └── <component-name>/
│ ├── <component-name>.md # history + intent for THIS component (scoped context)
│ ├── <component-name>.js # the custom element
│ └── <component-name>.css # (optional) styles consumed inside shadow DOM
└── web/
├── static/ # shared static assets (app.css tokens, etc. — NOT component-specific)
└── templates/ # Go html/template page shells (incl. help.html)
```
> If a new concern doesn't fit cleanly, **ask** before inventing a new toplevel
> directory. Keep `components/` strictly for web components. There is deliberately
> **no `migrations/` and no `db/`** — see Section 1.3.
---
## 4. UI layout & peruser state (no server DB)
Because there is no authentication and no database, **peruser UI state lives in
the browser** (`localStorage`), namespaced under a `gitmanager.*` prefix:
- Dockable panels: the **repo list docked LEFT**, the **repo detail panel docked
RIGHT** by default. Dock options are `top`, `left`, `bottom`, `right`; positions
are remembered in `localStorage` and reapplied on load.
- Other view state (selected repo, active filter/search, expanded sections, chosen
branch view) also persists in `localStorage`.
- A **reset control** clears every `gitmanager.*` key and reloads to firstload
defaults (mirror the pattern: view state only — never touch the repos).
Serverside there is no session and no user record. Requests act as the single
host operator.
---
## 5. Repo discovery & the refresh scanner (readonly)
**Git is truth; the index is a derived cache.** Implement in `internal/repos`,
using the `internal/git` boundary for every command.
- **Discovery:** walk each configured root (`GIT_REPO_ROOTS`) for directories
containing `.git`, honoring a configurable max depth and ignore globs. Build an
inmemory index keyed by absolute repo path.
- **Refresh (readonly):** for each repo compute status (dirty/clean, staged vs
unstaged counts), current branch, ahead/behind vs upstream, remotes, and a short
recentcommit summary — all via readonly Git commands (Section 1.3). Refresh on
a **configurable interval** and, optionally, on filesystem change via `fsnotify`.
- **Optional background `fetch`:** disabled by default. Only when
`SCAN_FETCH_ENABLED=true` may the scanner run `git fetch` (network, readonly to
the working tree) to keep ahead/behind counts current. Ratelimit it.
- **Never block a request on a slow repo.** The dashboard reads from the index;
the scanner updates the index in the background. A single unreachable remote or
huge repo must not stall the others (perrepo timeouts, isolated goroutines).
Resulting data path:
```
Host repositories (system of record)
│ read-only scan (status / rev-list / for-each-ref / log [/ fetch if enabled])
internal/repos ──► in-memory index (per-repo state)
Echo JSON endpoints ──► web components (self-fetch)
User action ──► internal/git (os/exec) ──► repository mutated ──► index refresh
```
---
## 6. Dashboard & Git operations (client side)
The dashboard is composed of web components, each obeying Section 1 (shadow DOM,
selffetching, independent lifecycle, cleanup on disconnect). Expected surface
(create each with its own folder + `.md` as you build it):
- A **repo list / grid** of all discovered repos with status badges (branch,
dirty/clean, ahead/behind), searchable/filterable.
- A **repo detail panel** for the selected repo: branches, remotes, recent commit
log, stashes, tags.
- **Diff / commit views** rendered from serverproduced unified diffs.
- A **rightclick context menu** on any item (repo, branch, PR, stash) is the
primary way commands are run. This is the app's reason for being (Section 0):
commands read in **plain language** for people who don't memorize Git — e.g.
"Get latest" (pull), "Save my work" (commit), "Publish" (push), "Merge &
clean up" (merge PR + delete branch). Keep a friendlyname → Git/forgeop
vocabulary; the same operations are exposed to Claude as MCP tools (Section 8),
both calling the one service layer (§1.7).
- An **action surface** for Git operations. Safe operations (fetch, pull,
checkout, create branch, stage, commit, push) can proceed on a normal click;
**destructive operations follow Section 1.4** (explicit control + confirmation
naming the target).
- Crosscomponent communication is via bubbling/composed `CustomEvent`s (e.g. a
`repo:select` event the detail panel listens for) — never shared globals.
Server endpoints return JSON for the components to selffetch; mutating endpoints
route through the service layer (§1.7) and trigger an index refresh for the
affected repo so the UI reflects reality without a full rescan.
---
## 7. Logging (structured, to stdout/stderr)
- Use Go's **`log/slog`** as the logging API throughout.
- Emit **structured records to stdout/stderr** (JSON in nondev, a readable
console handler in dev). Capture: timestamp, level, message, structured
attributes, request id, and the repo path when an operation targets one.
- **Log every Git mutation** (the command, target repo/branch, and outcome) so the
operator has an audit trail of what the tool did on their behalf.
- Optionally also write to a **rotating file** when `LOG_FILE` is set. There is no
database sink — do not add one (Section 1.3).
---
## 8. Claude integration (MCP server · activity feed · graceful handoff · forge)
This is the app's defining pillar (Section 0): GitManager works *with* Claude in
both directions. Everything here goes through the one service layer (§1.7) and
obeys the safety rules (§1.4).
### 8.1 MCP server — Claude drives the app (`internal/mcp`)
- The app serves an **MCP endpoint over Streamable HTTP at `/mcp`** using
`github.com/modelcontextprotocol/go-sdk`. Like the rest of the app it is
**localhostbound and unauthenticated** (Section 0) — do not expose it offhost.
- **How Claude Desktop connects — the local stdio bridge, NOT the GUI connector.**
The "Add custom connector" GUI is for **remote, publiclyreachable** servers:
it probes (and would call tools) **from Anthropic's cloud**, which cannot reach
`127.0.0.1`. So a localhost URL there fails "couldn't reach the server" even
though a local browser reaches it. The working path is a **local stdio bridge**
configured in `claude_desktop_config.json` under `mcpServers`, launched on the
user's machine:
```json
"gitmanager": { "command": "cmd",
"args": ["/c","npx","-y","mcp-remote","http://127.0.0.1:8080/mcp"] }
```
`mcp-remote` runs locally and speaks stdio to Claude Desktop, so it reaches the
local endpoint directly and needs **no public exposure and no HTTPS**. Do **not**
reach for a public tunnel — that would expose an unauthenticated repomanagement
app to the internet.
- **HTTPS is still available** (`:8443`, mkcert cert) for clients that require it,
but is not needed for the stdiobridge path above.
- MCP **tools are thin adapters** over the service layer — no Git/forge logic in
the tool handlers (§1.7). Expected tools (grow as features land):
- Read: `list_repos`, `get_repo`, `get_active_project`, `get_activity`,
`get_pending_switch`, `list_prs`.
- Act: `git_status/checkout/commit/push/pull/create_branch`,
`create_pr`, `merge_and_cleanup_pr`, `set_active_project`, `ack_switch`.
- **Destructive tools carry the §1.4 contract into MCP:** they describe exactly
what they will do and default to the safe variant. The confirmation is the
human's — surfaced through Claude and/or the app UI — not something the tool
silently assumes.
### 8.2 Activity feed & active project (`internal/activity`)
- The app keeps, in memory (and mirrored to the logs — no new datastore, §1.3):
- the **active project** (the single repo/task currently in focus), and
- an **activity feed** of what happened (user *and* Claude actions: repo
switched, committed, PR merged, …).
- Both are **queryable** (`get_active_project`, `get_activity`) so Claude can
**sync on any turn boundary** — the reliable, pullbased foundation. The app
also **pushes** these to the browser over SSE for live UI. This pullfirst
design does not depend on the host letting a connector wake Claude.
### 8.3 Graceful project handoff (the headline flow)
When the user switches project/task in the app, it is a **request**, not an
instant yank. The cooperative protocol:
1. **User** picks a new project/task in the app → the app records a
**pendingswitch request** (target + optional note) and the UI shows
"waiting for Claude to reach a good stopping point."
2. **Claude** sees the pending request (it checks at its natural turnboundary
checkpoints via `get_pending_switch`). It **finishes to a safe stopping
point and preserves work** — never abandons uncommitted changes to switch;
it completes the inflight step and commits/stashes as appropriate — then
performs the switch (`set_active_project`, moving its working context to the
new repo) and calls **`ack_switch`** with a short summary of where it left
the previous project.
3. **App** marks the request fulfilled and **notifies the user** over SSE
("Claude switched to *ProjectB*; *ProjectA* left at: …"). The user proceeds.
**Rule:** the switch is Claudecompleted at a checkpoint, not appforced. Losing
or interrupting uncommitted work to satisfy a switch is a §1.4class violation.
> Fully autonomous "Claude starts working the instant you click, with no turn
> from you" is intentionally **not** assumed — it depends on host push support.
> Build 8.28.3 pullfirst; layer any autowake on top only where the host allows.
### 8.4 Forge integration — read **and write** (`internal/forge`)
Talking to the hosting provider is isolated behind a **provider interface**
(**Gitea/Forgejo first** — the primary host is `git.nilles.net`; GitHub/GitLab
later behind the same interface).
- **Read:** list open PRs/MRs and their CI/check status for a repo whose remote
points at a supported host.
- **Write (enabled):** create a PR, **merge a PR, and delete the source branch**
— this powers "**Merge & clean up**", the feature that makes PRs usable for a
user who otherwise finds them clutter (Section 0). Every write is **confirmed
per §1.4**, names the PR/branch, and prefers the tidy default (squashmerge +
delete branch). Note a merged PR remains in the host's history; "clean up"
means removing the **branch**, not falsifying history.
- **Enablement is perhost and tokengated.** Tokens come from `.env`
(e.g. `GITEA_TOKEN`); with no token the forge features are simply absent and
the rest of the app works unchanged (**graceful degradation**).
- The provider is inferred from a repo's remote URL. Never send repo data to a
host the user didn't configure.
---
## 9. Selfdocumenting workflow (MANDATORY on every change)
This is how the project documents itself so AGENT.md stays small and each
component carries its own context.
### 9.1 Root `CHANGELOG.md`
- **On every change you make**, append an entry to root `CHANGELOG.md`. Never
rewrite history; only append. Each entry:
```
## YYYY-MM-DD — <short title>
- **What:** what changed (files, behavior).
- **Why:** the intent / the request behind it.
- **Affects:** components/areas touched.
```
### 9.2 Percomponent `<component-name>.md`
- When you **create a component**, create `components/<name>/<name>.md` with:
```
# <component-name>
## Intent
What this component is for; the ActiveX-spirit contract it fulfills.
## Public surface
Tag name, attributes/properties, emitted events, what data it fetches and from where.
## History
- YYYY-MM-DD: created — <reason>.
- YYYY-MM-DD: <change> — <reason>.
## Notes / gotchas
```
- When you **work on an existing component**, **read its `.md` first** (it is the
scoped context for that component), make the change, then **append to its
History** and update Public surface if it changed.
### 9.3 Userfacing help page
- `web/templates/help.html` (served at `/help`, linked from the app header) explains
**how to use the app** for end users. **When a userfacing feature changes** — a new
control, a changed workflow, a removed/renamed option — **update the help page in the
same change**, the way you update the `CHANGELOG`. Keep it taskoriented (how to do
things), not implementation detail. Document any host prerequisites (SSH agent /
credential helper for pushing from the container).
### 9.4 Keep AGENT.md lean
- Componentspecific detail lives in the component's `.md`, **not here**. Once a
component has its own `.md`, **move any componentspecific detail out of
AGENT.md** into that file. AGENT.md stays architecturelevel only.
- If a change alters an **architectural law or stack choice** in this file,
update AGENT.md too — but only architecturelevel facts belong here.
---
## 10. How to take a new task ("add X functionality")
1. **Read** this AGENT.md, then any relevant component `.md` files.
2. **Check the rules** in Section 1. If the request conflicts, **pause and ask.**
3. If the work is UI: it is a **web component** in `components/<name>/` with its
own `.md`. No exceptions without asking.
4. If it touches repositories or forges: put the logic in the **service layer**
(§1.7) over the `internal/git` / `internal/forge` boundaries — never in an HTTP
or MCP handler — so both the GUI and Claude get it. Respect **Git = system of
record** (§1.3), treat **destructive operations** per §1.4, and never add a
datastore. A new capability generally means: service method → HTTP handler →
MCP tool → UI control.
5. If it needs a **new dependency** or a **new toplevel folder**, propose it and
wait for approval.
6. Implement, run it in the **dockercompose dev environment**, verify hot reload
and that it operates correctly against a real mounted repo.
7. **Document:** append to `CHANGELOG.md`; create/append the component `.md`;
update `help.html` if userfacing; trim AGENT.md if component detail crept in.
---
## 11. Open items to confirm before/while building
*(Claude Code: surface these to the human at the first relevant moment; don't
silently guess.)*
- **Repo discovery strategy:** recursive scan of `GIT_REPO_ROOTS` (max depth?
ignore globs?) vs an explicit list of repo paths. Default assumption: recursive
scan with a configurable depth.
- **Background `fetch`:** off by default (no unsolicited network). Confirm whether
it should be enabled, and the interval / rate limit, before turning it on.
- **Git access library:** system `git` via `os/exec` is the locked default
(Section 2). Confirm before introducing `go-git` for any read path.
- ✅ **RESOLVED 2026-09-19:** **Forge = Gitea/Forgejo first** (`git.nilles.net`),
**read + write** — merge PR + delete branch ("Merge & clean up"), each confirmed
per §1.4 (Section 8.4). GitHub/GitLab later behind the same interface.
- ✅ **RESOLVED 2026-09-19:** **MCP transport = Streamable HTTP at `/mcp`**, added
in Claude Desktop as a custom connector (Section 8.1).
- ✅ **RESOLVED 2026-09-19:** **Project handoff is cooperative** — user requests a
switch, Claude finishes to a safe checkpoint, switches, and the user is notified;
pullfirst, not autonomous (Section 8.3).
- ✅ **RESOLVED 2026-09-20:** Claude Desktop's **GUI "custom connector" cannot
reach a localhost server** — it validates/calls from Anthropic's cloud. Solved
with the **local stdio bridge** (`claude_desktop_config.json` → `mcpServers` →
`mcp-remote http://127.0.0.1:8080/mcp`), §8.1. (HTTPS on `:8443` via mkcert was
added earlier and still works, but is not required for this path.)
- **Idletrigger for handoff:** the pull model syncs at Claude's turn boundaries.
If Claude is idle when the user switches, decide the nudge (user's next message,
a heartbeat/poll, or a host push if available) — do not assume instant wake.
- **Coordinationstate lifetime:** active project / pendingswitch / activity feed
are inmemory today (§1.3). Confirm if any must survive an app restart before
adding any persistence.
- **Gitea token scope:** which token scopes to require (repo read + PR write +
branch delete) and how it is provisioned; document in `.env.example`.
- **Listen address / exposure:** localhostonly by default (covers `/mcp` too).
Confirm before binding to a nonlocal interface — there is no auth (Section 0).
- **Credential path from the container:** SSH agent socket vs mounted keys vs
credential helper, for pushing/fetching from inside Docker.
---
*End of AGENT.md. Keep it lean. Let the CHANGELOG and percomponent `.md` files
carry the detail.*