# CLAUDE.md — **SPB Git** · Personal Git Platform for Simon-Pierre Boucher > **Read this entire document before writing a single line of code.** > This file is the single source of truth for the project. Every architectural decision, naming convention, UI detail, and non-negotiable rule lives here. When in doubt, re-read this file. When this file conflicts with your instinct, this file wins. --- ## 0. Identity & Non-Negotiables | Key | Value | |---|---| | **Product name** | SPB Git | | **Owner / sole user** | Simon-Pierre Boucher | | **Contact** | [contact@spboucher.ai](mailto:contact@spboucher.ai) | | **Public domain** | `https://git.spboucher.ai` (ngrok custom domain) | | **Deployment host** | Node **m3u96a** | | **Runtime** | Node.js ≥ 20, ESM only | | **Visibility model** | **ALL repositories are PUBLIC (read-only)**. Write access = owner only. No sign-up, no visitor accounts, ever. | ### 0.1 THE GOLDEN RULE — Mandatory Author Header **Every single file you generate — source, config, script, style, test, docs tooling — MUST begin with an author header.** No exceptions. A file without this header is a bug. Canonical template (adapt comment syntax per language): ```js /** * ───────────────────────────────────────────── * SPB Git — Personal Git Platform * ───────────────────────────────────────────── * Author : Simon-Pierre Boucher * Contact : contact@spboucher.ai * File : * Purpose : * License : MIT © Simon-Pierre Boucher * ───────────────────────────────────────────── */ ``` Per-language syntax: | Language | Syntax | |---|---| | JS / TS / CSS / SCSS | `/** ... */` block | | Python / Bash / YAML / Dockerfile / TOML | `#` lines | | HTML / Vue / Markdown (when appropriate) | `` | | SQL | `-- ` lines | | EJS / Nunjucks templates | `<%# ... %>` / `{# ... #}` | Additionally, the server itself must **inject/verify** this header: a lint script `npm run check:headers` fails CI if any tracked file in *this* project lacks the header. Provide `scripts/inject-headers.mjs` to add headers in bulk. --- ## 1. Product Vision SPB Git is not a toy. It is a **polished, self-hosted software forge** — the personal equivalent of GitHub — that serves three purposes: 1. **Canonical home** for all of Simon-Pierre's repositories, clonable by anyone at `https://git.spboucher.ai/.git`. 2. **Public showcase / portfolio**: every repo page must look as good as (or better than) a GitHub repo page — rendered README with badges, syntax-highlighted code browsing, commit graphs, language stats. 3. **Frictionless daily driver**: a powerful CLI (`spbgit`) that manages *all* repos from the terminal — create, commit, push, sync, open — in single commands. The bar for quality is: *"If a stranger lands on git.spboucher.ai, they should assume a small team built this."* --- ## 2. High-Level Architecture ``` ┌──────────────────────────── node m3u96a ────────────────────────────┐ │ │ │ ┌─────────────┐ ┌───────────────────────────────────────────────┐ │ │ │ ngrok │ │ SPB Git Server (Node 20) │ │ │ │ tunnel │──▶│ Fastify app :7420 │ │ │ │ git.spboucher│ │ ├─ / Web UI (SSR, Nunjucks) │ │ │ │ .ai │ │ ├─ /api/v1/* JSON API │ │ │ └─────────────┘ │ ├─ /:repo.git/* Git Smart HTTP (backend) │ │ │ │ ├─ /raw/* Raw file serving │ │ │ │ └─ /archive/* zip/tar.gz snapshots │ │ │ └───────────────┬───────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┼───────────────────────────┐ │ │ │ │ │ │ │ ┌──────▼──────┐ ┌────────────────▼─────────┐ ┌──────────────▼───┐ │ │ │ /srv/git/ │ │ /srv/spbgit/data/ │ │ /srv/spbgit/ │ │ │ │ *.git │ │ meta.json (repo index) │ │ cache/ │ │ │ │ bare repos │ │ tokens, hooks, stats │ │ rendered HTML, │ │ │ │ (source of │ │ │ │ lang stats, zips │ │ │ │ truth) │ │ │ │ │ │ │ └─────────────┘ └──────────────────────────┘ └──────────────────┘ │ └───────────────────────────────────────────────────────────────────────┘ ``` **Principles:** - **Filesystem is the database.** Bare Git repos under `/srv/git/` are the source of truth. A single `meta.json` holds per-repo metadata (description, topics, pinned, homepage, created date). No SQL/NoSQL database. - **Cache aggressively, invalidate on push.** Rendered READMEs, language stats, commit counts, and archives are cached in `/srv/spbgit/cache/` keyed by `@`; a post-receive hook busts the cache. - **SSR-first web UI.** Server-rendered pages (Nunjucks templates + a small vanilla JS layer). No SPA framework. Fast, crawlable, sharp. ### 2.1 Repository Layout of *this* project ``` spbgit/ ├── CLAUDE.md # this file ├── README.md # project readme (with badges, of course) ├── package.json ├── src/ │ ├── server.mjs # Fastify bootstrap │ ├── config.mjs # env + config loading (zod-validated) │ ├── git/ │ │ ├── smart-http.mjs # upload-pack / receive-pack over HTTP │ │ ├── repo.mjs # repo model: branches, tree, blob, log, blame │ │ ├── hooks.mjs # post-receive hook installer + handlers │ │ └── archive.mjs # zip / tar.gz snapshot generation │ ├── auth/ │ │ └── token.mjs # owner PAT verification (argon2-hashed) │ ├── render/ │ │ ├── markdown.mjs # GFM pipeline (markdown-it + plugins) │ │ ├── highlight.mjs # code highlighting (shiki) │ │ └── og-image.mjs # dynamic Open Graph card per repo (satori/resvg) │ ├── api/ │ │ └── v1.mjs # JSON API routes │ ├── web/ │ │ ├── routes.mjs # HTML routes │ │ ├── views/ # Nunjucks templates │ │ └── assets/ # css (hand-written, design tokens), js, fonts, logo │ └── stats/ │ ├── languages.mjs # linguist-style language detection + percentages │ └── activity.mjs # commit calendar / punch-card data ├── cli/ │ ├── spbgit.mjs # entry point (bin) │ └── commands/ # one file per command ├── deploy/ │ ├── ngrok.yml │ ├── ecosystem.config.cjs # pm2: server + ngrok, autorestart │ ├── spbgit.service # systemd alternative │ ├── setup-m3u96a.sh # idempotent server bootstrap script │ └── backup.sh # nightly tar of /srv/git + meta.json ├── scripts/ │ ├── inject-headers.mjs │ └── check-headers.mjs └── test/ # vitest: unit + integration (smart HTTP round-trip) ``` --- ## 3. Git Hosting Engine ### 3.1 Smart HTTP protocol - Implement Git Smart HTTP v2 by spawning `git upload-pack` / `git receive-pack` (via `git http-backend` or direct spawn with proper pkt-line streaming — **stream, never buffer** packfiles). - Routes: - `GET /:repo.git/info/refs?service=git-upload-pack` → public (clone/fetch/pull). - `POST /:repo.git/git-upload-pack` → public. - `GET /:repo.git/info/refs?service=git-receive-pack` → **auth required**. - `POST /:repo.git/git-receive-pack` → **auth required** (push). - Auth for push: HTTP Basic where username = `spb` and password = a **Personal Access Token**. Tokens are generated by `spbgit token new`, stored argon2-hashed in `data/tokens.json`, revocable, with a label and last-used timestamp. - Set correct `Content-Type`, chunked encoding, gzip request body support (`Content-Encoding: gzip` from git clients), and side-band progress passthrough. - Repo names: `^[a-z0-9][a-z0-9._-]{0,63}$`. Reject path traversal hard. ### 3.2 Post-receive hooks (installed automatically in every bare repo) On every push, the hook POSTs to `http://127.0.0.1:7420/internal/hooks/post-receive` (localhost-only route) which: 1. Busts all caches for that repo. 2. Recomputes language stats + default-branch README render (warm cache). 3. Appends an event to the **activity feed** (`data/activity.jsonl`). 4. Regenerates the repo's OG image. ### 3.3 Repo lifecycle - `POST /api/v1/repos` (auth) → `git init --bare`, install hooks, write `meta.json` entry, set `HEAD` to `refs/heads/main`. - `PATCH /api/v1/repos/:name` (auth) → description, topics (max 10), homepage URL, pinned flag, default branch. - `DELETE /api/v1/repos/:name` (auth) → moves repo to `/srv/git/.trash/-.git` (soft delete, recoverable for 30 days). - Everything is public: there is **no** `private` field anywhere in the codebase. Don't build one. --- ## 4. Web UI — "It must look sharp" (Design Spec) ### 4.1 Design system - **Aesthetic:** modern developer-forge. Dark theme by default, light theme toggle (persisted in `localStorage`, respects `prefers-color-scheme` on first visit). - **Design tokens** (CSS custom properties in `assets/css/tokens.css`): - Dark: background `#0b0e14`, surface `#11151c`, border `#1f2530`, text `#e6e9ef`, muted `#8b93a3`, accent `#4f8cff`, accent-2 `#22d3aa`, danger `#ff5d5d`. - Radius `10px`, subtle 1px borders, no heavy shadows; hover states elevate with border-color shifts, not box-shadows. - **Typography:** `Inter` (UI) + `JetBrains Mono` (code), self-hosted woff2 in `assets/fonts/` — no third-party font CDNs. - **Layout:** max-width `1200px`, sticky top nav: logo "**SPB Git**" (monogram SVG), global search input (`/` focuses it), theme toggle, link to `spboucher.ai`. - Fully responsive down to 360px. Code views scroll horizontally, never break the page. - Footer on every page: `© Simon-Pierre Boucher · contact@spboucher.ai · Powered by SPB Git`. ### 4.2 Pages **`/` — Home / repo index** - Hero strip: avatar/monogram, "Simon-Pierre Boucher", tagline, contact link, count badges (X repos · Y commits · Z languages). - **Pinned repos** grid (up to 6 cards) then full repo list. - Repo card: name, description, top language with colored dot (GitHub linguist colors), star-like "clone count" if tracked, last-pushed relative time ("3 h ago"), topic chips. - Controls: search-as-you-type filter, sort by *Recently pushed / Name / Created*, filter by language and topic. - **Activity feed** sidebar: last 15 pushes ("pushed 3 commits to `api-tools` · 2 h ago"). - **Commit calendar** (GitHub-style contribution heatmap, last 52 weeks, SVG, tooltip per day). **`/:repo` — Repo home** - Header: repo name (breadcrumb `spb / name`), description, topic chips, homepage link, language bar (stacked percentage bar like GitHub, with legend). - **Clone box**: HTTPS URL with copy button + `spbgit clone ` snippet + "Download ZIP". - Meta row: default branch selector, branch/tag counts, commit count, repo size, license badge (auto-detected from `LICENSE`). - File table for the current tree: icon, name, last commit message touching that path, relative date (computed efficiently via a single `git log` walk, cached). - **README.md rendered below the file table.** See §5 — this must be pixel-perfect. **`/:repo/tree/:ref/*path`** — directory browsing (same file table pattern). **`/:repo/blob/:ref/*path`** — file view: - Sticky header: path breadcrumb, size, LOC, buttons: **Raw**, **Blame**, **History**, **Copy permalink** (permalink pins the commit SHA). - Shiki-highlighted code with line numbers; clickable line anchors (`#L42`, ranges `#L10-L20` with highlight). - Markdown files render as HTML by default with a "View source" toggle. Images display inline. Jupyter notebooks: at minimum render as pretty JSON with a note (nice-to-have: nbconvert-style render). - Binary files: show size + download button; images/SVG preview inline. **`/:repo/commits/:ref`** — paginated history (40/page): avatar-less but with identicon per author email, message (first line, expandable body), short SHA chip (copy on click), relative + absolute date, files-changed count. **`/:repo/commit/:sha`** — full diff view: per-file collapsible diffs, syntax-highlighted, green/red gutters, stats header (+X −Y across N files), rename/binary detection. **`/:repo/blame/:ref/*path`** — blame view with commit hunks grouped and color-aged (newer = brighter accent). **`/:repo/branches`, `/:repo/tags`** — lists with ahead/behind counts vs default branch (branches) and archive download links (tags → releases-lite). **`/search?q=`** — global search across repo names, descriptions, topics, and README content (build a tiny inverted index in cache; refresh on push). Grouped results. **Error pages** — custom 404/500 with the design system (404: "This ref does not exist in any timeline."). ### 4.3 Meta / polish - Every page: proper ``, meta description, canonical URL, **Open Graph + Twitter card** with the dynamically generated per-repo OG image (repo name, description, language bar — generated with satori → resvg, cached). - `sitemap.xml` + `robots.txt` + RSS/Atom feed of activity at `/feed.atom`. - Favicon set (SVG + PNG) with the SPB monogram. - Lighthouse targets: Performance ≥ 95, A11y ≥ 95. No render-blocking third-party requests at all. --- ## 5. README & Markdown Rendering — Badge-Perfect (Critical) This is a flagship feature. The rendered README **must be indistinguishable from GitHub's rendering quality**. Pipeline (`src/render/markdown.mjs`), built on `markdown-it` +: - **GFM tables**, **task lists** (rendered checkboxes, disabled), **strikethrough**, **autolinks**, **footnotes**. - **Heading anchors** with hover link icon; auto-generated slug IDs (GitHub algorithm) so `#installation` links work. - **Syntax highlighting** in fenced blocks via **shiki** (same themes as file view; dual dark/light output via CSS variables). Support ` ```mermaid ` blocks → render client-side with mermaid.js (lazy-loaded only when present). - **Badges & images:** - Badges (`img.shields.io`, etc.) must render **inline, on one line, correctly sized** — CSS: `img[src*="shields.io"], img[height] { display: inline-block; vertical-align: middle; }` and consecutive badge images separated by single spaces must not wrap awkwardly. - **Relative image paths resolve to the raw endpoint**: `./docs/demo.png` → `/raw/:repo/:ref/docs/demo.png`. Same for relative links → blob URLs. This is mandatory; broken relative images are a failing grade. - `<img>`/`<picture>`/`<details>`/`<summary>`/`<kbd>`/`<sup>`/`<sub>`/`align` attributes: allowed. Sanitize with a strict allowlist (DOMPurify-style via `sanitize-html`): no scripts, no event handlers, no iframes (except a curated allowlist: YouTube embeds off by default). - **HTML in Markdown** (centered headers, badge tables — the classic GitHub README style) must render correctly. - Emoji shortcodes (`:rocket:` → 🚀). - Look for `README.md` case-insensitively, then `readme.rst`/`README.txt` fallbacks (plain render). - Cache the rendered HTML per `(repo, sha, theme-agnostic)`. Typography of rendered Markdown mirrors GitHub's `.markdown-body`: 16px base, 1.6 line-height, bordered tables with zebra rows, blockquote left-border in muted accent, `code` chips with surface background, hr as 1px border. --- ## 6. `spbgit` CLI — Terminal Command Center A standalone Node CLI (`cli/spbgit.mjs`, exposed as `bin: { "spbgit": ... }`, installable via `npm i -g` from the repo or `npm link`). Zero heavy deps — use `commander` (or hand-rolled parser), `picocolors` for output, native `fetch`. ### 6.1 Configuration `~/.spbgit/config.json`: ```json { "server": "https://git.spboucher.ai", "token": "<PAT>", "workspace": "~/code", "author": { "name": "Simon-Pierre Boucher", "email": "contact@spboucher.ai" } } ``` - `spbgit init` — interactive setup wizard (server URL, token paste, workspace dir); sets `git config` user.name/email globally if missing; chmod 600 the config. - Token is **never** printed back, never logged, never in argv. ### 6.2 Commands (full spec) | Command | Behavior | |---|---| | `spbgit init` | Setup wizard (above). | | `spbgit token new [--label "laptop"]` / `token list` / `token revoke <id>` | Manage PATs via API. | | `spbgit list` (`ls`) | Table of server repos: name, description, language, last push, clone-URL. `--json` for scripting. | | `spbgit create <name> [-d "desc"] [--topics a,b] [--push]` | Create on server; with `--push`, also init current dir, add remote `origin`, create initial commit, push. | | `spbgit clone <name> [dir]` | Clone into workspace (or dir). | | `spbgit clone --all` | Clone every server repo missing from the workspace. | | `spbgit status [name\|--all]` | Aggregated status across all workspace repos: branch, ahead/behind, dirty file count. One aligned, colorized row per repo; clean repos dimmed. | | `spbgit commit <name\|--all> -m "msg"` | `git add -A && git commit` in target repo(s). Skips clean repos with a note. | | `spbgit push [name\|--all]` | Push current branch; `--all` iterates every workspace repo with commits ahead. | | `spbgit pull [name\|--all]` | Pull with `--ff-only` by default; `--rebase` flag. | | `spbgit sync [-m "msg"]` | **The killer command**: for every workspace repo → add-all, commit (default msg `chore: sync YYYY-MM-DD HH:mm`), pull --rebase, push. Summary table at the end: ✓ synced / ↑ pushed n / ✗ conflict (with instructions). Never force-pushes. | | `spbgit open [name]` | Opens `https://git.spboucher.ai/<name>` (or home) in browser (`open`/`xdg-open`/`start`). | | `spbgit info <name>` | Server-side details: size, branches, commit count, topics, clone URL. | | `spbgit rm <name> --confirm` | Soft-delete on server (types the repo name to confirm, GitHub-style). | | `spbgit doctor` | Diagnoses: config present, token valid (API ping), server reachable, git installed, workspace exists. | ### 6.3 CLI UX rules - Colorized, aligned output; respects `NO_COLOR` and `--json` everywhere. - Exit codes: 0 ok, 1 user error, 2 network/auth, 3 partial failure in `--all` operations. - All multi-repo operations run with a concurrency limit of 4 and stream per-repo progress lines. - Helpful errors: on 401 → "Token invalid or revoked. Run `spbgit token new` then `spbgit init`." --- ## 7. JSON API (`/api/v1`) Public reads, token-gated writes (`Authorization: Bearer <PAT>`). - `GET /api/v1/repos` · `GET /api/v1/repos/:name` · `GET /api/v1/repos/:name/languages` · `GET /api/v1/repos/:name/commits?ref&page` - `POST /api/v1/repos` · `PATCH /api/v1/repos/:name` · `DELETE /api/v1/repos/:name` (auth) - `POST /api/v1/tokens` · `GET /api/v1/tokens` · `DELETE /api/v1/tokens/:id` (auth; creation of the *first* token happens server-side via `deploy/setup` script printing a bootstrap token once) - `GET /api/v1/stats` — totals for the home hero. - Rate limit public endpoints (200 req/min/IP, `@fastify/rate-limit`), sane JSON error envelope `{ "error": { "code", "message" } }`. --- ## 8. Deployment — node m3u96a + ngrok + `git.spboucher.ai` ### 8.1 `deploy/setup-m3u96a.sh` (idempotent) 1. Install Node 20 (via nvm or distro), git, ngrok. 2. Create dirs: `/srv/git`, `/srv/spbgit/{data,cache}`, correct ownership. 3. Clone/pull this repo to `/srv/spbgit/app`, `npm ci --omit=dev`. 4. Generate bootstrap PAT, print it **once** to stdout. 5. Install pm2 config (or systemd unit) and start. ### 8.2 ngrok (`deploy/ngrok.yml`) ```yaml version: 3 agent: authtoken: ${NGROK_AUTHTOKEN} endpoints: - name: spbgit url: https://git.spboucher.ai upstream: url: http://127.0.0.1:7420 ``` - Document the DNS step: CNAME `git.spboucher.ai` → the ngrok-provided edge target, plus domain registration in the ngrok dashboard. - ngrok provides TLS termination; the app still sets `trustProxy: true` and derives client IPs from `x-forwarded-for` for rate limiting. ### 8.3 Process management (`deploy/ecosystem.config.cjs`) - Two pm2 apps: `spbgit-server` (`node src/server.mjs`) and `spbgit-tunnel` (`ngrok start --config deploy/ngrok.yml spbgit`), both `autorestart: true`, `max_restarts` sane, logs to `/srv/spbgit/logs/`. - `pm2 save` + `pm2 startup` documented so everything survives reboot of m3u96a. - Provide the systemd unit as the documented alternative. ### 8.4 Backups & ops - `deploy/backup.sh` — nightly cron: `tar.zst` of `/srv/git` + `data/` into `/srv/spbgit/backups/`, keep 14, log result. - `GET /healthz` (uptime, repo count, cache size) for monitoring; pm2 healthcheck hits it. - Structured logs (pino), request logging with latency, log rotation. --- ## 9. Security Checklist (verify each before "done") - [ ] Path traversal impossible in repo names, tree paths, raw endpoint (normalize + verify prefix). - [ ] Push requires valid PAT; tokens argon2-hashed at rest; constant-time compare. - [ ] Rendered Markdown sanitized (no script/style/iframe/event-handlers) while keeping badges, `<details>`, alignment HTML working. - [ ] Raw endpoint serves with `Content-Type` sniffing disabled (`X-Content-Type-Options: nosniff`) and correct types; HTML files served as `text/plain` from `/raw` to prevent stored-XSS-via-repo. - [ ] CSP header on web UI (self + data: images + shields.io/img allowlist for remote images in READMEs — use a relaxed `img-src *` since READMEs embed arbitrary badges, but everything else locked down). - [ ] Localhost-only guard on `/internal/*` hook routes. - [ ] No secrets in code, logs, or error messages. `.env` + zod-validated config. --- ## 10. Quality Bar & Definition of Done **Engineering standards:** ESM, small modules, JSDoc on exported functions, vitest coverage on the git plumbing (tree/log/blame parsing) and an end-to-end test that spins the server, creates a repo via API, pushes via real `git` CLI, and asserts the web UI + API reflect it. `npm run lint` (eslint) + `npm run check:headers` both green. **The platform is DONE when every box below is checked:** - [ ] `bash deploy/setup-m3u96a.sh` on a fresh m3u96a gets the whole stack live at `https://git.spboucher.ai` and survives a reboot. - [ ] `git clone https://git.spboucher.ai/anything.git` works for a stranger with no auth; pushing without a PAT is rejected with 401. - [ ] `spbgit init` → `spbgit create demo --push` → repo instantly visible on the home page with correct language stats. - [ ] `spbgit sync` walks every workspace repo and prints an accurate summary table. - [ ] A README containing a badge row, a centered HTML header, a mermaid diagram, a task list, relative images, and code blocks renders flawlessly in dark **and** light theme. - [ ] Repo pages have working file browser, blob view with line anchors, commits, single-commit diff, blame, branches, tags, ZIP download. - [ ] Home page shows pinned repos, activity feed, and the contribution heatmap. - [ ] Every generated file in the codebase carries the **Simon-Pierre Boucher / contact@spboucher.ai** header (`npm run check:headers` passes). - [ ] OG image cards, sitemap, Atom feed, custom 404 — all present. - [ ] Lighthouse ≥ 95 / 95 on home and a repo page. --- ## 11. Build Order (follow this sequence) 1. **Skeleton**: config, Fastify bootstrap, logging, healthz, header scripts. 2. **Git core**: bare-repo model + Smart HTTP (clone/push round-trip test passes) + hooks + PAT auth. 3. **API v1** + `meta.json` model. 4. **CLI** (init → create → push → sync working end-to-end against local server). 5. **Web UI**: design tokens → layout → home → repo page → README pipeline (badges!) → blob/commits/diff/blame → search/stats/heatmap. 6. **Polish**: OG images, feeds, error pages, a11y pass, Lighthouse. 7. **Deploy**: scripts, ngrok, pm2, backups; go live on m3u96a; run the full DoD checklist. Work in small commits with conventional-commit messages (`feat:`, `fix:`, `chore:`). At the end of each phase, state which DoD boxes are now satisfied.