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 |
| 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):
/**
* ─────────────────────────────────────────────
* SPB Git — Personal Git Platform
* ─────────────────────────────────────────────
* Author : Simon-Pierre Boucher
* Contact : contact@spboucher.ai
* File : <relative/path/filename.ext>
* Purpose : <one-line description>
* 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:
- Canonical home for all of Simon-Pierre's repositories, clonable by anyone at
https://git.spboucher.ai/<repo>.git. - 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.
- 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 singlemeta.jsonholds 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<repo>@<commit-sha>; 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(viagit http-backendor 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 =
spband password = a Personal Access Token. Tokens are generated byspbgit token new, stored argon2-hashed indata/tokens.json, revocable, with a label and last-used timestamp. - Set correct
Content-Type, chunked encoding, gzip request body support (Content-Encoding: gzipfrom 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:
- Busts all caches for that repo.
- Recomputes language stats + default-branch README render (warm cache).
- Appends an event to the activity feed (
data/activity.jsonl). - Regenerates the repo's OG image.
3.3 Repo lifecycle
POST /api/v1/repos(auth) →git init --bare, install hooks, writemeta.jsonentry, setHEADtorefs/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/<name>-<timestamp>.git(soft delete, recoverable for 30 days).- Everything is public: there is no
privatefield 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, respectsprefers-color-schemeon 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.
- Dark: background
- Typography:
Inter(UI) +JetBrains Mono(code), self-hosted woff2 inassets/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 tospboucher.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 <name>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 logwalk, 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-L20with 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
<title>, 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
#installationlinks work. - Syntax highlighting in fenced blocks via shiki (same themes as file view; dual dark/light output via CSS variables). Support
```mermaidblocks → 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>/alignattributes: allowed. Sanitize with a strict allowlist (DOMPurify-style viasanitize-html): no scripts, no event handlers, no iframes (except a curated allowlist: YouTube embeds off by default).
- Badges (
- HTML in Markdown (centered headers, badge tables — the classic GitHub README style) must render correctly.
- Emoji shortcodes (
:rocket:→ 🚀). - Look for
README.mdcase-insensitively, thenreadme.rst/README.txtfallbacks (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:
{
"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); setsgit configuser.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_COLORand--jsoneverywhere. - Exit codes: 0 ok, 1 user error, 2 network/auth, 3 partial failure in
--alloperations. - 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 newthenspbgit 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&pagePOST /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 viadeploy/setupscript 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)
- Install Node 20 (via nvm or distro), git, ngrok.
- Create dirs:
/srv/git,/srv/spbgit/{data,cache}, correct ownership. - Clone/pull this repo to
/srv/spbgit/app,npm ci --omit=dev. - Generate bootstrap PAT, print it once to stdout.
- Install pm2 config (or systemd unit) and start.
8.2 ngrok (deploy/ngrok.yml)
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: trueand derives client IPs fromx-forwarded-forfor rate limiting.
8.3 Process management (deploy/ecosystem.config.cjs)
- Two pm2 apps:
spbgit-server(node src/server.mjs) andspbgit-tunnel(ngrok start --config deploy/ngrok.yml spbgit), bothautorestart: true,max_restartssane, logs to/srv/spbgit/logs/. pm2 save+pm2 startupdocumented so everything survives reboot of m3u96a.- Provide the systemd unit as the documented alternative.
8.4 Backups & ops
deploy/backup.sh— nightly cron:tar.zstof/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-Typesniffing disabled (X-Content-Type-Options: nosniff) and correct types; HTML files served astext/plainfrom/rawto 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.shon a fresh m3u96a gets the whole stack live athttps://git.spboucher.aiand survives a reboot. -
git clone https://git.spboucher.ai/anything.gitworks 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 syncwalks 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:headerspasses). - 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)
- Skeleton: config, Fastify bootstrap, logging, healthz, header scripts.
- Git core: bare-repo model + Smart HTTP (clone/push round-trip test passes) + hooks + PAT auth.
- API v1 +
meta.jsonmodel. - CLI (init → create → push → sync working end-to-end against local server).
- Web UI: design tokens → layout → home → repo page → README pipeline (badges!) → blob/commits/diff/blame → search/stats/heatmap.
- Polish: OG images, feeds, error pages, a11y pass, Lighthouse.
- 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.