📄 octuna / AGENTS.md
| 1 | # otgit agent rules |
| 2 | |
| 3 | A self-hosted git host with a 1999 Microsoft-era UI. Octuna's CSS shell, otgit's brain. |
| 4 | Read this file before every change. Follow it without exception. |
| 5 | |
| 6 | ## Mission |
| 7 | |
| 8 | Portable, self-hosted, open source git host. Lightweight, low RAM, fast, secure. |
| 9 | Retro skin, modern feature set covering the must-haves and nothing more. |
| 10 | Useful enough to pick over a folder of zips. Never bloated. |
| 11 | |
| 12 | ## Chosen architecture: layered minimal |
| 13 | |
| 14 | Decided by scored comparison. Raw node `http`, git binary subprocess for all repo |
| 15 | ops, JSON state with atomic write, scrypt + HMAC session cookies. Logic is split |
| 16 | into small reusable modules under `lib/` so values, security checks, and git ops |
| 17 | live in exactly one place. Routes are thin and call into those modules. |
| 18 | |
| 19 | ## The five hard limits |
| 20 | |
| 21 | 1. RAM: server target under 60 MB resident. Cap node heap at 64 MB. No in-memory |
| 22 | caches of file contents. Stream everything from disk. |
| 23 | 2. Dependencies: at most two runtime npm deps. Default to one (`busboy`). Add a |
| 24 | second only if it removes more code than it adds. Never add a framework. |
| 25 | 3. Source file size: no file over 400 lines. Plan a split before adding more. |
| 26 | 4. CSS: total under 1000 lines across all files. Reuse classes. |
| 27 | 5. No hardcoded tunables. Every magic number, path, limit, label, or feature |
| 28 | flag comes from `config.get(...)`. If a value would appear in two files, it |
| 29 | goes in config or in `lib/`. |
| 30 | |
| 31 | If a change breaks any of these, stop and surface it before continuing. |
| 32 | |
| 33 | ## Reusability rule |
| 34 | |
| 35 | If a piece of logic is used twice, it lives in `lib/` and both callers import it. |
| 36 | If a value is referenced twice, it lives in `config.json` and both callers read |
| 37 | it through `config.get()`. No exceptions for "it's just a small string". This is |
| 38 | the rule that prevents drift. |
| 39 | |
| 40 | ## File layout |
| 41 | |
| 42 | ``` |
| 43 | F:\otgit\ |
| 44 | server.js boot, register routes, listen |
| 45 | setup.js create admin user, generate secrets, write config |
| 46 | config.json runtime, generated |
| 47 | config.example.json shipped |
| 48 | package.json |
| 49 | AGENTS.md |
| 50 | README.md |
| 51 | lib\ |
| 52 | router.js pattern match, :param compile, dispatch |
| 53 | respond.js json, html, text, redirect, security headers |
| 54 | auth.js scrypt, HMAC sessions, requireAuth, requireOwner |
| 55 | limit.js rate limit factory |
| 56 | store.js atomic JSON read, debounced write |
| 57 | config.js layered loader, get, set, requiresRestart list |
| 58 | git.js spawn wrappers: init, log, tree, blob, diff, etc |
| 59 | http-backend.js smart HTTP CGI bridge |
| 60 | render.js template fill, html escape, markdown, diff to html |
| 61 | validate.js names, paths, schemas, prefix safety |
| 62 | log.js small leveled logger |
| 63 | routes\ |
| 64 | auth.js signup, login, logout, me, password |
| 65 | user.js user profile and repo list |
| 66 | repo.js repo home, tree, blob, history, single commit |
| 67 | repo-write.js create, rename, visibility, delete |
| 68 | smart-http.js git push and pull |
| 69 | admin.js admin settings UI and save |
| 70 | static.js cached static asset serve |
| 71 | public\ |
| 72 | style.css shell, ported from Octuna |
| 73 | pages.css page specific styles, diff, tree, code |
| 74 | app.js shared client glue |
| 75 | logo.png from Octuna |
| 76 | views\ |
| 77 | layout.html page chrome with content slot |
| 78 | home.html |
| 79 | auth.html |
| 80 | user.html |
| 81 | repo-home.html |
| 82 | repo-tree.html |
| 83 | repo-blob.html |
| 84 | repo-history.html |
| 85 | repo-commit.html |
| 86 | repo-settings.html |
| 87 | admin-settings.html |
| 88 | data\ |
| 89 | users.json |
| 90 | repos.json |
| 91 | repos\<owner>\<name>.git\ |
| 92 | scripts\ |
| 93 | smoke.js end to end smoke test |
| 94 | ``` |
| 95 | |
| 96 | ## Config schema |
| 97 | |
| 98 | `config.json` is the single source of truth for tunables. Admin web UI writes |
| 99 | it through `config.set(path, value)` which validates and atomically rewrites |
| 100 | the file. Fields marked restart in `lib/config.js#REQUIRES_RESTART` show a |
| 101 | warning in the admin UI. |
| 102 | |
| 103 | ``` |
| 104 | site |
| 105 | name string display name |
| 106 | publicUrl string external URL, controls cookieSecure when auto |
| 107 | bindHost string restart |
| 108 | port number restart |
| 109 | tagline string |
| 110 | auth |
| 111 | sessionSecret base64 restart, generated by setup |
| 112 | sessionMaxAgeDays number |
| 113 | scryptN number restart effectively, only for new hashes |
| 114 | allowSignup bool |
| 115 | repos |
| 116 | maxPerUser number |
| 117 | maxRepoSizeMB number |
| 118 | defaultVisibility public or private |
| 119 | allowPrivate bool |
| 120 | defaultBranch string |
| 121 | limits |
| 122 | login { max, windowMin } |
| 123 | signup { max, windowMin } |
| 124 | write { max, windowMin } |
| 125 | push { max, windowMin } |
| 126 | uploadMaxMB number |
| 127 | security |
| 128 | trustProxy bool |
| 129 | cookieSecure auto or true or false |
| 130 | csp string |
| 131 | ui |
| 132 | addressBarMode real or fixed |
| 133 | addressBarFixed string |
| 134 | marqueeText string |
| 135 | footerText string |
| 136 | themeColor teal or navy or purple |
| 137 | git |
| 138 | binPath string |
| 139 | httpBackendPath string |
| 140 | admin |
| 141 | user string restart on change |
| 142 | ``` |
| 143 | |
| 144 | Setup writes `auth.sessionSecret`, `admin.user`, and the admin password hash. |
| 145 | Every other field has a default in `lib/config.js`. |
| 146 | |
| 147 | ## Security rules |
| 148 | |
| 149 | - Passwords: scrypt N=16384, r=8, p=1, 16 byte salt per user, 64 byte hash. |
| 150 | - Sessions: HMAC SHA256 over `user.issued`, base64url. Verify with |
| 151 | `crypto.timingSafeEqual`. Cookie HttpOnly, SameSite=Strict, Secure when |
| 152 | publicUrl is https or `security.cookieSecure` is true. |
| 153 | - Rate limits: every limit value comes from config. No literal numbers in route |
| 154 | files. Smart HTTP push gets its own bucket. |
| 155 | - Path safety: validate owner and repo names against |
| 156 | `^[a-z0-9][a-z0-9_-]{0,38}$`. Always `path.resolve` and assert the resolved |
| 157 | path starts with the configured repos root. |
| 158 | - `git http-backend` runs with `GIT_PROJECT_ROOT` pinned to the resolved repo |
| 159 | root. Never let request data choose the repo path. |
| 160 | - Public repo reads anonymous. Private reads and any write require a session |
| 161 | whose user owns the repo. Push reuses the login credentials over basic auth. |
| 162 | - CSP from config. No inline scripts except a single config blob, hashed in |
| 163 | CSP. No third party origins. |
| 164 | - All HTML output escaped. Markdown renderer is a safe subset, no raw HTML. |
| 165 | - Never `exec`. Always `spawn` with argv arrays. |
| 166 | - Never log passwords, session tokens, or full cookies. |
| 167 | - Setup forbids weak admin passwords (length under 10 or in a tiny common list). |
| 168 | |
| 169 | ## Performance rules |
| 170 | |
| 171 | - Stream files. Never `readFileSync` a blob to send it. |
| 172 | - Blob viewer caps render at `repos.maxRenderBlobMB` (default 1). Larger files |
| 173 | show metadata and a raw download link only. |
| 174 | - Diff viewer caps rendered diff at 5000 lines. Larger diffs link to raw. |
| 175 | - Cache static assets in memory once at boot. Nothing else cached in RAM. |
| 176 | - Read repo metadata lazily from git. Do not maintain shadow indices. |
| 177 | |
| 178 | ## Code style |
| 179 | |
| 180 | - No em dashes anywhere, in code or comments or text. Use commas, periods, or |
| 181 | parentheses. The character `—` is banned in this repo. |
| 182 | - No cheesy AI prose. No "Let's", "I'll go ahead", "Certainly!", no emojis in |
| 183 | comments, no decorative banners. Plain, terse, technical. |
| 184 | - Comments only when the why is non obvious. Never restate the code. |
| 185 | - Identifiers say what they are. Functions short, under 40 lines. If a handler |
| 186 | grows, extract. |
| 187 | - No abstractions for hypothetical future use. Three similar lines beats a |
| 188 | premature helper, but two distinct call sites for the same logic must share. |
| 189 | - No try catch that swallows. Either handle or let it bubble. |
| 190 | - No backwards compat shims for code we wrote ourselves yesterday. |
| 191 | |
| 192 | ## CSS rules |
| 193 | |
| 194 | - Reuse Octuna's variables and components. Do not redefine the shell tokens. |
| 195 | - Page specific styles in `pages.css`. `style.css` stays generic. |
| 196 | - Plain hand written CSS. No framework, no preprocessor. |
| 197 | - Audit total CSS line count at every change. Hard cap 1000. |
| 198 | |
| 199 | ## Self critique protocol |
| 200 | |
| 201 | After every plan, every step, every commit, end with a Critique block: |
| 202 | |
| 203 | ``` |
| 204 | Critique: |
| 205 | - Weakest part of this, specific. |
| 206 | - Anything that violates the rules: RAM, deps, line count, CSS budget, em dash, |
| 207 | cheesy text, hardcoded value, security shortcut. |
| 208 | - Anything skipped that the spec asked for. |
| 209 | - Next risk. |
| 210 | ``` |
| 211 | |
| 212 | If the critique surfaces a rule violation, fix it before continuing. Do not |
| 213 | mark a step complete with an open critique item unaddressed. |
| 214 | |
| 215 | ## v1 feature checklist |
| 216 | |
| 217 | Must ship: |
| 218 | - Signup, login, logout, change password |
| 219 | - Create repo public or private, rename, toggle visibility, delete |
| 220 | - Repo home: rendered README, file tree, branch dropdown, commit count |
| 221 | - File tree browser |
| 222 | - Blob viewer with line numbers and total line count |
| 223 | - Commit history list and single commit diff view with red and green hunks |
| 224 | - Push and pull over HTTPS smart protocol with basic auth |
| 225 | - User profile page listing the user's visible repos |
| 226 | - Admin settings page with full backend control over config.json |
| 227 | - Per repo settings page |
| 228 | - Issues |
| 229 | - Releases and Source download .zip |
| 230 | - Stars |
| 231 | |
| 232 | Out of scope: |
| 233 | - pull requests, stars, forks, webhooks, CI, wikis, gists, |
| 234 | organizations, SSH transport, federation. |
| 235 | |
| 236 | If you find yourself building something not on the must ship list, stop. |
| 237 | |
| 238 | ## Testing |
| 239 | |
| 240 | - `scripts/smoke.js` hits every route, asserts status codes, exercises a real |
| 241 | push and pull against a temp git client. Run after every change. |
| 242 | - No mocks. Real server, real git, real disk. |
| 243 | |
| 244 | ## Build order (suggested, may revise per critique) |
| 245 | |
| 246 | 1. Skeleton: `lib/config.js`, `lib/store.js`, `lib/respond.js`, `lib/router.js`, |
| 247 | `lib/log.js`, `lib/validate.js`, `server.js` boot. |
| 248 | 2. Auth: `lib/auth.js`, `lib/limit.js`, `routes/auth.js`, `setup.js`, login UI. |
| 249 | 3. Repos read path: `lib/git.js`, `routes/repo.js`, `routes/user.js`, repo |
| 250 | views, blob viewer, history, diff. |
| 251 | 4. Repo write path: `routes/repo-write.js`, repo settings view. |
| 252 | 5. Smart HTTP: `lib/http-backend.js`, `routes/smart-http.js`, push and pull. |
| 253 | 6. Admin settings: `routes/admin.js`, admin view. |
| 254 | 7. Theme port: `style.css`, `pages.css`, layout chrome. |
| 255 | 8. Smoke test, RAM check, CSS line audit, doc the README. |
| 256 | |
| 257 | After each step, run smoke and write the critique. |
| 258 | |
| 259 | ## When in doubt |
| 260 | |
| 261 | Pick the option with less code, less RAM, less surface area. If two options tie, |
| 262 | pick the one easier for a stranger to read in five years. |
| 263 |