# otgit agent rules

A self-hosted git host with a 1999 Microsoft-era UI. Octuna's CSS shell, otgit's brain.
Read this file before every change. Follow it without exception.

## Mission

Portable, self-hosted, open source git host. Lightweight, low RAM, fast, secure.
Retro skin, modern feature set covering the must-haves and nothing more.
Useful enough to pick over a folder of zips. Never bloated.

## Chosen architecture: layered minimal

Decided by scored comparison. Raw node `http`, git binary subprocess for all repo
ops, JSON state with atomic write, scrypt + HMAC session cookies. Logic is split
into small reusable modules under `lib/` so values, security checks, and git ops
live in exactly one place. Routes are thin and call into those modules.

## The five hard limits

1. RAM: server target under 60 MB resident. Cap node heap at 64 MB. No in-memory
   caches of file contents. Stream everything from disk.
2. Dependencies: at most two runtime npm deps. Default to one (`busboy`). Add a
   second only if it removes more code than it adds. Never add a framework.
3. Source file size: no file over 400 lines. Plan a split before adding more.
4. CSS: total under 1000 lines across all files. Reuse classes.
5. No hardcoded tunables. Every magic number, path, limit, label, or feature
   flag comes from `config.get(...)`. If a value would appear in two files, it
   goes in config or in `lib/`.

If a change breaks any of these, stop and surface it before continuing.

## Reusability rule

If a piece of logic is used twice, it lives in `lib/` and both callers import it.
If a value is referenced twice, it lives in `config.json` and both callers read
it through `config.get()`. No exceptions for "it's just a small string". This is
the rule that prevents drift.

## File layout

```
F:\otgit\
  server.js                  boot, register routes, listen
  setup.js                   create admin user, generate secrets, write config
  config.json                runtime, generated
  config.example.json        shipped
  package.json
  AGENTS.md
  README.md
  lib\
    router.js                pattern match, :param compile, dispatch
    respond.js               json, html, text, redirect, security headers
    auth.js                  scrypt, HMAC sessions, requireAuth, requireOwner
    limit.js                 rate limit factory
    store.js                 atomic JSON read, debounced write
    config.js                layered loader, get, set, requiresRestart list
    git.js                   spawn wrappers: init, log, tree, blob, diff, etc
    http-backend.js          smart HTTP CGI bridge
    render.js                template fill, html escape, markdown, diff to html
    validate.js              names, paths, schemas, prefix safety
    log.js                   small leveled logger
  routes\
    auth.js                  signup, login, logout, me, password
    user.js                  user profile and repo list
    repo.js                  repo home, tree, blob, history, single commit
    repo-write.js            create, rename, visibility, delete
    smart-http.js            git push and pull
    admin.js                 admin settings UI and save
    static.js                cached static asset serve
  public\
    style.css                shell, ported from Octuna
    pages.css                page specific styles, diff, tree, code
    app.js                   shared client glue
    logo.png                 from Octuna
  views\
    layout.html              page chrome with content slot
    home.html
    auth.html
    user.html
    repo-home.html
    repo-tree.html
    repo-blob.html
    repo-history.html
    repo-commit.html
    repo-settings.html
    admin-settings.html
  data\
    users.json
    repos.json
    repos\<owner>\<name>.git\
  scripts\
    smoke.js                 end to end smoke test
```

## Config schema

`config.json` is the single source of truth for tunables. Admin web UI writes
it through `config.set(path, value)` which validates and atomically rewrites
the file. Fields marked restart in `lib/config.js#REQUIRES_RESTART` show a
warning in the admin UI.

```
site
  name           string   display name
  publicUrl      string   external URL, controls cookieSecure when auto
  bindHost       string   restart
  port           number   restart
  tagline        string
auth
  sessionSecret  base64   restart, generated by setup
  sessionMaxAgeDays number
  scryptN        number   restart effectively, only for new hashes
  allowSignup    bool
repos
  maxPerUser     number
  maxRepoSizeMB  number
  defaultVisibility public or private
  allowPrivate   bool
  defaultBranch  string
limits
  login          { max, windowMin }
  signup         { max, windowMin }
  write          { max, windowMin }
  push           { max, windowMin }
  uploadMaxMB    number
security
  trustProxy     bool
  cookieSecure   auto or true or false
  csp            string
ui
  addressBarMode real or fixed
  addressBarFixed string
  marqueeText    string
  footerText     string
  themeColor     teal or navy or purple
git
  binPath        string
  httpBackendPath string
admin
  user           string   restart on change
```

Setup writes `auth.sessionSecret`, `admin.user`, and the admin password hash.
Every other field has a default in `lib/config.js`.

## Security rules

- Passwords: scrypt N=16384, r=8, p=1, 16 byte salt per user, 64 byte hash.
- Sessions: HMAC SHA256 over `user.issued`, base64url. Verify with
  `crypto.timingSafeEqual`. Cookie HttpOnly, SameSite=Strict, Secure when
  publicUrl is https or `security.cookieSecure` is true.
- Rate limits: every limit value comes from config. No literal numbers in route
  files. Smart HTTP push gets its own bucket.
- Path safety: validate owner and repo names against
  `^[a-z0-9][a-z0-9_-]{0,38}$`. Always `path.resolve` and assert the resolved
  path starts with the configured repos root.
- `git http-backend` runs with `GIT_PROJECT_ROOT` pinned to the resolved repo
  root. Never let request data choose the repo path.
- Public repo reads anonymous. Private reads and any write require a session
  whose user owns the repo. Push reuses the login credentials over basic auth.
- CSP from config. No inline scripts except a single config blob, hashed in
  CSP. No third party origins.
- All HTML output escaped. Markdown renderer is a safe subset, no raw HTML.
- Never `exec`. Always `spawn` with argv arrays.
- Never log passwords, session tokens, or full cookies.
- Setup forbids weak admin passwords (length under 10 or in a tiny common list).

## Performance rules

- Stream files. Never `readFileSync` a blob to send it.
- Blob viewer caps render at `repos.maxRenderBlobMB` (default 1). Larger files
  show metadata and a raw download link only.
- Diff viewer caps rendered diff at 5000 lines. Larger diffs link to raw.
- Cache static assets in memory once at boot. Nothing else cached in RAM.
- Read repo metadata lazily from git. Do not maintain shadow indices.

## Code style

- No em dashes anywhere, in code or comments or text. Use commas, periods, or
  parentheses. The character `—` is banned in this repo.
- No cheesy AI prose. No "Let's", "I'll go ahead", "Certainly!", no emojis in
  comments, no decorative banners. Plain, terse, technical.
- Comments only when the why is non obvious. Never restate the code.
- Identifiers say what they are. Functions short, under 40 lines. If a handler
  grows, extract.
- No abstractions for hypothetical future use. Three similar lines beats a
  premature helper, but two distinct call sites for the same logic must share.
- No try catch that swallows. Either handle or let it bubble.
- No backwards compat shims for code we wrote ourselves yesterday.

## CSS rules

- Reuse Octuna's variables and components. Do not redefine the shell tokens.
- Page specific styles in `pages.css`. `style.css` stays generic.
- Plain hand written CSS. No framework, no preprocessor.
- Audit total CSS line count at every change. Hard cap 1000.

## Self critique protocol

After every plan, every step, every commit, end with a Critique block:

```
Critique:
- Weakest part of this, specific.
- Anything that violates the rules: RAM, deps, line count, CSS budget, em dash,
  cheesy text, hardcoded value, security shortcut.
- Anything skipped that the spec asked for.
- Next risk.
```

If the critique surfaces a rule violation, fix it before continuing. Do not
mark a step complete with an open critique item unaddressed.

## v1 feature checklist

Must ship:
- Signup, login, logout, change password
- Create repo public or private, rename, toggle visibility, delete
- Repo home: rendered README, file tree, branch dropdown, commit count
- File tree browser
- Blob viewer with line numbers and total line count
- Commit history list and single commit diff view with red and green hunks
- Push and pull over HTTPS smart protocol with basic auth
- User profile page listing the user's visible repos
- Admin settings page with full backend control over config.json
- Per repo settings page
- Issues
- Releases and Source download .zip
- Stars

Out of scope:
- pull requests, stars, forks, webhooks, CI, wikis, gists,
  organizations, SSH transport, federation.

If you find yourself building something not on the must ship list, stop.

## Testing

- `scripts/smoke.js` hits every route, asserts status codes, exercises a real
  push and pull against a temp git client. Run after every change.
- No mocks. Real server, real git, real disk.

## Build order (suggested, may revise per critique)

1. Skeleton: `lib/config.js`, `lib/store.js`, `lib/respond.js`, `lib/router.js`,
   `lib/log.js`, `lib/validate.js`, `server.js` boot.
2. Auth: `lib/auth.js`, `lib/limit.js`, `routes/auth.js`, `setup.js`, login UI.
3. Repos read path: `lib/git.js`, `routes/repo.js`, `routes/user.js`, repo
   views, blob viewer, history, diff.
4. Repo write path: `routes/repo-write.js`, repo settings view.
5. Smart HTTP: `lib/http-backend.js`, `routes/smart-http.js`, push and pull.
6. Admin settings: `routes/admin.js`, admin view.
7. Theme port: `style.css`, `pages.css`, layout chrome.
8. Smoke test, RAM check, CSS line audit, doc the README.

After each step, run smoke and write the critique.

## When in doubt

Pick the option with less code, less RAM, less surface area. If two options tie,
pick the one easier for a stranger to read in five years.
