d3abd4416e
The go-sdk infers each tool's outputSchema from its handler result
type, and MCP structured output must be type "object". list_repos
returned []repos.State, yielding outputSchema.type "array", which
Claude Desktop rejects at tools/list — taking the whole server down.
Wrap the slice in listReposOutput{Repos: ...} so the schema is an
object, update the round-trip test, and record the struct-result rule
in AGENT.md §8.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
555 lines
31 KiB
Markdown
555 lines
31 KiB
Markdown
# 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 non‑negotiable
|
||
> rules, and the self‑documenting 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, long‑lived development application: a **multi‑repo 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 — per‑repo
|
||
status (clean/dirty, ahead/behind, current branch), branches, remotes, recent
|
||
commits, stashes, and tags — plus user‑initiated Git actions (fetch, pull, push,
|
||
checkout, create branch, commit, view diffs, manage remotes). It has per‑user
|
||
dockable UI panels and is architected so that host/forge integrations
|
||
(GitHub/GitLab pull‑ and merge‑requests) can be attached without touching the
|
||
core.
|
||
|
||
**The larger purpose — a two‑way 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/clean‑up 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 plain‑language, right‑click 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
|
||
self‑documenting workflow in Section 9 intact.
|
||
|
||
**Trust model:** there is **no user authentication**. The app is a single‑operator
|
||
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. Non‑negotiable 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 (ActiveX‑spirit, enforced)
|
||
|
||
The UI is built **exclusively** from native Web Components. The design intent is
|
||
deliberately modeled on the *self‑contained control* philosophy of Windows‑98‑era
|
||
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.
|
||
- **Self‑instantiating**: the page declares the element in markup (or via a thin
|
||
loader). The browser "constructs" it. The page does not micro‑manage 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.
|
||
- **Self‑contained**: no shared mutable global state. Cross‑component
|
||
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.
|
||
- **Lifecycle‑correct**: implement `connectedCallback` / `disconnectedCallback`
|
||
and clean up timers, listeners, and in‑flight 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, dependency‑free helpers like
|
||
`lit` *may* be proposed, but only with explicit approval, because `lit` still
|
||
produces standards‑based custom elements.)
|
||
|
||
**Shared styling — design tokens.** The one sanctioned cross‑shadow‑boundary
|
||
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 Git‑state 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 & per‑component docs
|
||
|
||
- There is a **top‑level `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. Component‑specific 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,
|
||
read‑optimized in‑memory 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, user‑initiated 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 read‑only.** 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 per‑user UI state (browser `localStorage`, Section 4). Adding any other
|
||
datastore (SQLite, a server‑side 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 / docker‑compose
|
||
|
||
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**
|
||
(read‑write, 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 host‑side
|
||
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 server‑rendered 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. Pure‑Go `go-git` may be proposed for cheap read‑only queries, but only behind the same interface and only with approval. |
|
||
| Repo discovery / index | **In‑memory 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) | **Provider‑abstracted** (`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**, token‑gated per host. Writes (merge PR, delete branch) are confirmed per §1.4. See Section 8. The primary host is a self‑hosted 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. |
|
||
| Real‑time (app → browser) | **Server‑Sent 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 top‑level
|
||
> directory. Keep `components/` strictly for web components. There is deliberately
|
||
> **no `migrations/` and no `db/`** — see Section 1.3.
|
||
|
||
---
|
||
|
||
## 4. UI layout & per‑user state (no server DB)
|
||
|
||
Because there is no authentication and no database, **per‑user 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 first‑load
|
||
defaults (mirror the pattern: view state only — never touch the repos).
|
||
|
||
Server‑side there is no session and no user record. Requests act as the single
|
||
host operator.
|
||
|
||
---
|
||
|
||
## 5. Repo discovery & the refresh scanner (read‑only)
|
||
|
||
**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
|
||
in‑memory index keyed by absolute repo path.
|
||
- **Refresh (read‑only):** for each repo compute status (dirty/clean, staged vs
|
||
unstaged counts), current branch, ahead/behind vs upstream, remotes, and a short
|
||
recent‑commit summary — all via read‑only 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, read‑only to
|
||
the working tree) to keep ahead/behind counts current. Rate‑limit 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 (per‑repo 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,
|
||
self‑fetching, 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 server‑produced unified diffs.
|
||
- A **right‑click 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 friendly‑name → Git/forge‑op
|
||
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).
|
||
- Cross‑component 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 self‑fetch; 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 non‑dev, 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
|
||
**localhost‑bound and unauthenticated** (Section 0) — do not expose it off‑host.
|
||
- **How Claude Desktop connects — the local stdio bridge, NOT the GUI connector.**
|
||
The "Add custom connector" GUI is for **remote, publicly‑reachable** 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 repo‑management
|
||
app to the internet.
|
||
- **HTTPS is still available** (`:8443`, mkcert cert) for clients that require it,
|
||
but is not needed for the stdio‑bridge 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`.
|
||
- **A tool's result type must be a struct, never a bare slice/map/scalar.** The
|
||
go-sdk infers each tool's `outputSchema` from its handler's result type, and MCP
|
||
structured output must be a JSON **object** (`type: "object"`). A handler that
|
||
returns `[]T` yields `outputSchema.type: "array"`, which Claude Desktop rejects
|
||
at `tools/list` — and one bad tool takes the whole server down. Wrap any
|
||
collection result in a named output struct (e.g. `list_repos` returns
|
||
`listReposOutput{ Repos []repos.State }`, not `[]repos.State`). Fixed 2026-09-20.
|
||
- **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, pull‑based foundation. The app
|
||
also **pushes** these to the browser over SSE for live UI. This pull‑first
|
||
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
|
||
**pending‑switch 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 turn‑boundary
|
||
checkpoints via `get_pending_switch`). It **finishes to a safe stopping
|
||
point and preserves work** — never abandons uncommitted changes to switch;
|
||
it completes the in‑flight 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 Claude‑completed at a checkpoint, not app‑forced. Losing
|
||
or interrupting uncommitted work to satisfy a switch is a §1.4‑class 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.2–8.3 pull‑first; layer any auto‑wake 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 (squash‑merge +
|
||
delete branch). Note a merged PR remains in the host's history; "clean up"
|
||
means removing the **branch**, not falsifying history.
|
||
- **Enablement is per‑host and token‑gated.** 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. Self‑documenting 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 Per‑component `<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 User‑facing 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 user‑facing 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 task‑oriented (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
|
||
- Component‑specific detail lives in the component's `.md`, **not here**. Once a
|
||
component has its own `.md`, **move any component‑specific detail out of
|
||
AGENT.md** into that file. AGENT.md stays architecture‑level only.
|
||
- If a change alters an **architectural law or stack choice** in this file,
|
||
update AGENT.md too — but only architecture‑level 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 top‑level folder**, propose it and
|
||
wait for approval.
|
||
6. Implement, run it in the **docker‑compose 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 user‑facing; 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;
|
||
pull‑first, 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.)
|
||
- **Idle‑trigger 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.
|
||
- **Coordination‑state lifetime:** active project / pending‑switch / activity feed
|
||
are in‑memory 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:** localhost‑only by default (covers `/mcp` too).
|
||
Confirm before binding to a non‑local 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 per‑component `.md` files
|
||
carry the detail.*
|