# 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. 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//`. - **Each component folder contains its own `.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. --- ## 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`) — GitHub via `github.com/google/go-github`, GitLab via `gitlab.com/gitlab-org/api/client-go` | Optional, read‑only by default, enabled per‑host when a token is configured. See Section 8. | | 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 │ ├── forge/ # OPTIONAL seam: GitHub/GitLab PR/MR + remote metadata (provider-abstracted) │ ├── logging/ # slog handler → stdout/stderr (+ optional file) │ └── render/ # html/template page shell rendering ├── components/ # ALL web components live here (Section 1.2) │ └── / │ ├── .md # history + intent for THIS component (scoped context) │ ├── .js # the custom element │ └── .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. - 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 `internal/git` 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. Forge integration — OPTIONAL seam (PRs / MRs) Viewing pull/merge requests requires talking to a hosting provider, which is **outside** the "Git is the store" core. Keep it isolated and optional. - Live in `internal/forge` behind a **provider interface** so GitHub, GitLab, and others can drop in without touching the dashboard or the Git boundary. - **Read‑only by default**: list open PRs/MRs and their CI/check status for a repo whose remote points at a supported host. Any write action (comment, merge, approve) is a **separate, explicitly‑requested** capability — do not build write paths without asking, and route them through Section 1.4 if destructive. - **Enablement is per‑host and token‑gated.** Tokens come from `.env` (e.g. `GITHUB_TOKEN`, `GITLAB_TOKEN`); with no token, the feature is simply absent and the rest of the app works unchanged (**graceful degradation** — never a hard dependency). - The provider is inferred from a repo's remote URL. Never send repo data to a host the user didn't configure. Design the interface cleanly now; implement providers incrementally as requested. --- ## 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 — - **What:** what changed (files, behavior). - **Why:** the intent / the request behind it. - **Affects:** components/areas touched. ``` ### 9.2 Per‑component `.md` - When you **create a component**, create `components//.md` with: ``` # ## 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 — . - YYYY-MM-DD: . ## 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//` with its own `.md`. No exceptions without asking. 4. If it touches repositories: go through the **`internal/git` boundary**, respect **Git = system of record** (Section 1.3), and treat **destructive operations** per Section 1.4. Never add a datastore. 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. - **Forge providers:** which to support first (GitHub? GitLab?), and whether any **write** actions (merge/comment/approve) are ever in scope (default: read‑only). - **Listen address / exposure:** localhost‑only by default. 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.*