# AGENTS.md ## Purpose This repository is a Git Smart HTTP v2 server implemented on Cloudflare Workers with Durable Objects, R2, KV, React SSR, and a small amount of client-side hydration. Write changes against the current source tree, not the docs alone. Some documentation is slightly behind the live layout. ## Rules from the user - Reuse existing types/helpers/methods. Do not invent new types/helpers/method unnecessarily, especially do not use any or casting. - Prefer clarity over cleverness. Favor explicit names and intermediate types over dense inline expressions. Small duplication is acceptable when it materially improves readability and maintenance. - Comment the code, especially around nuanced behaviors and footguns. This is also not an excuse to be lean on comments. - Prioritize lookup on `cloudflare-docs` mcp for up-to-date Cloudflare developer docs (if available). Fallback to searching the web if you cannot find the relevant information on this mcp ### Avoid transitive boundary crossings - Try to keep cross-runtime boundaries to a single hop from the caller. Workers -> DO, Workers -> R2, or DO -> R2 are allowed, but do not chain boundaries transitively. In particular, Workers -> DO -> R2 is not allowed in most circumstances. - Retain strict mutation boundaries between R2, Workers, and Durable Object: Durable Objects can hold a transaction against its object. Do not attempt to propose a design that resembles a distributed transaction (e.g. Workers reads a row, decide on resolutions, then invoke Durable Object RPC to mutate) - If there needs to be resolution, Durable Object can resolve that conflict in a transaction and return a tagged union for Workers to decide - If you need to reach for `blockConcurrencyWhile` in an Durable Object RPC, your design is probably wrong. Try again. - In general: Workers stays stateless, no mutations; Durable Objects are stateful, transactional. ### Error handling - Durable Objects do not throw. Use tagged union between Workers and Durable Objects to communicate outcome. - Reserve `throw` in Durable Object for FUBAR ### No ad-hoc or duplicated types - Before introducing a type, check whether a canonical one already exists. Key hubs: `src/worker/git/core/objects.ts` (GitObjectType), `src/worker/common/hex.ts` (OID helpers, zeroOid), `src/worker/git/object-store/support.ts` (typeCodeToObjectType), `src/worker/git/operations/limits.ts` (Limiter). - Do not use `ReturnType<>`, `Awaited>`, or inline union literals when a named type already covers the shape. Extract a named type alias if one doesn't exist yet. - Do not duplicate helpers. If you need `isZeroOid` or `typeCodeToObjectType`, import the existing one—don't redefine it locally. - Watch for unused imports left behind after refactors; remove them. ### Keep comments in sync with code - When modifying behavior, update every comment, JSDoc, and inline note that describes the old behavior in the same change. Outdated comments are worse than no comments. - Review neighboring comments when editing a function—if the surrounding prose no longer matches the logic, fix it. ### Remove implementation-phase language - Do not leave wording like "Phase 1/2/3", "TODO: next phase", "streaming-push WIP", or similar milestone markers in code, comments, or filenames once the feature is merged or the phase boundary is no longer meaningful. These create confusion for future readers. - If a prior change left such references and you're editing the same area, clean them up. ### Visibility logging on new code paths - Every non-trivial code path that touches R2, DO RPC, or background work must include structured logging using `createLogger` from `src/worker/common/logger.ts` (or the DO's `this.logger`). - Follow the established conventions: kebab-case message identifiers scoped by component (e.g. `"receive:finalize-committed"`), appropriate log level (debug for flow, info for state changes, warn for recoverable errors, error for hard failures), and structured extra fields with relevant context (oid, packKey, counts, etc.). - When adding a branch or error path to an existing function that already logs, add matching visibility for the new path—don't leave silent gaps. ### Limiter usage on platform-bound calls - Every R2 read/write and outbound DO RPC in a request-scoped code path must go through the `Limiter` from `src/worker/git/operations/limits.ts` via `limiter.run(label, fn)`. Obtain the limiter with `getLimiter(cacheCtx)` or pass it through options. - Use a descriptive label prefixed by target (e.g. `"r2:get-pack"`, `"do:get-object-compat"`). - Respect the subrequest budget (`DEFAULT_SUBREQUEST_BUDGET = 900`). If the code path has its own budget (like `RECEIVE_SUBREQUEST_BUDGET`), use `countSubrequest()` to track it. - Never bypass the limiter for "just one call"—the hard 1000-subrequest and 6-concurrent-connection ceilings apply to the entire request, not individual call sites. ## Stack At A Glance - Runtime: Cloudflare Workers with `nodejs_als` - Language: TypeScript ESM, strict mode - UI: React 19 SSR via `react-dom/server`, client islands, Tailwind CSS v4, Vite - Storage: Durable Object storage, Durable Object SQLite via `drizzle-orm/durable-sqlite`, R2, KV - Routing: `itty-router` - Path alias: `@/*` maps to `src/*` - Formatting: Prettier only; no ESLint config is present ## First Files To Read - `src/worker/index.ts`: top-level router registration and route ordering - `src/worker/routes/git.ts`: Git Smart HTTP endpoints, upload-pack/receive-pack behavior - `src/worker/routes/admin.ts`: owner-authenticated admin JSON endpoints - `src/worker/routes/auth.ts`: auth UI and auth API endpoints - `src/worker/do/repo/repoDO.ts`: repository Durable Object — metadata authority, receive leases, compaction, and background work - `src/worker/do/repo/db/dal.ts`: the required access layer for SQLite-backed repo metadata - `src/client/server/render.tsx` and `src/client/server/registry.tsx`: SSR view registration and rendering - `wrangler.jsonc`: bindings, vars, assets handling, compatibility date ## Directory Map - `src/worker/routes/`: HTTP route registration and route handlers - `src/worker/do/repo/`: repository Durable Object, receive, compaction, idle cleanup, storage, DB - `src/worker/do/auth/`: authentication Durable Object - `src/worker/git/core/`: low-level Git protocol parsing and object helpers - `src/worker/git/operations/`: fetch, read-path logic, streaming upload-pack implementation - `src/worker/git/pack/`: pack assembly, pack indexing, pack metadata helpers - `src/client/pages/`: route-level React pages - `src/client/components/`: shared SSR components - `src/client/islands/`: client-only interactive modules - `src/client/entries/`: Vite client entrypoints used by SSR pages - `src/shared/web/`: browser-safe request parsing, formatting, MIME/JSON helpers - `src/worker/common/`: Worker response, logging, compression, stubs, progress helpers - `test/`: Vitest worker integration tests and Node unit tests - `docs/`: architecture and API notes; useful, but verify against source before relying on path details ## Core Invariants - Route order matters. `registerAuthRoutes(router)` must stay before `registerUiRoutes(router)` so `/auth` is not shadowed by `/:owner`. - The Worker owns HTML routing. `wrangler.jsonc` sets `assets.html_handling` to `"none"`; do not move page ownership into the assets layer by accident. - The repo Durable Object is the source of truth for a single repository. Keep refs/HEAD authority there. - SQLite access for repo metadata must go through `src/worker/do/repo/db/dal.ts`. Do not add ad hoc raw Drizzle queries in unrelated files. - `RepoDurableObject.fetch()` intentionally exposes only a small HTTP surface. Keep typed RPC methods as the default internal interface. - Receive uses a lease model: one active receive lease at a time, acquired via `beginReceive()` and committed via `finalizeReceive()`. Concurrent pushes receive `503 Retry-After: 10`. - Git fetch paths are streaming-sensitive. Avoid unnecessary buffering on upload-pack and pack assembly paths. - Git pushes require a D1-backed PAT with `level = "push"`; HTTP Basic username must match the route namespace slug. - UI rendering goes through `renderUiView()` and the view registry in `src/client/server/registry.tsx`. New pages should plug into that system rather than inventing a parallel renderer. ## Normal Workflow For Agents 1. Check repo state first with `git status --short`. 2. Read the smallest relevant slice of the codebase before editing. 3. Prefer narrow changes in the subsystem that owns the behavior. 4. Run the smallest useful validation commands before finishing. 5. Update tests when behavior changes. Dirty worktrees are normal here. Do not overwrite or revert unrelated user changes. ## Commands ### Install and run ```bash npm install npm run dev ``` ### Build and static validation ```bash npm run build npm run typecheck npm run format:check ``` ### Formatting ```bash npm run format ``` ### Tests `npm run test` runs Node Vitest tests for non-worker units in `test/**/*.test.ts` excluding `*.worker.test.ts`. `npm run test:workers` runs Vitest against Cloudflare worker integration tests. `npm run test:auth` runs only `test/auth.worker.test.ts`. The 42 MiB pack-indexer fixture test is opt-in. Use `PACK_INDEXER_FIXTURE=1 npx vitest run --config vitest.config.ts test/pack-indexer-fixture.worker.test.ts` when you intentionally want to run it. Targeted commands: ```bash npx vitest run --config vitest.unit.config.ts test/object-parse.test.ts npx vitest run --config vitest.config.ts test/streaming-receive.worker.test.ts npx vitest run --config vitest.config.ts test/auth.worker.test.ts ``` ### Cloudflare and schema maintenance ```bash npm run cf-typegen npm run db:generate ``` Do not edit generated migrations under `drizzle/repo-do/` manually. Treat `src/worker/do/repo/db/schema.ts` as the source of truth: make the schema change there first, then run `npm run db:generate` to generate the migration. ## Validation By Change Type - Git protocol, DO, pack, compaction, caching, or routing changes: run `npm run typecheck` and the relevant worker tests in `test/*.worker.test.ts` - Auth changes: run `npm run test:auth` - Pure parsing or helper changes: run the targeted Node Vitest test plus `npm run typecheck` - UI-only SSR/component changes: run `npm run typecheck`; if route behavior changed, add relevant worker coverage - SQLite schema or DAL changes: run `npm run db:generate`, `npm run typecheck`, and the worker tests that cover the affected flow ## UI Notes - Design context (audience, brand personality, aesthetic direction, color/typography choices) lives in `PRODUCT.md` at the project root. Read it before making visual or UX decisions. - SSR pages live in `src/client/pages/`. - Shared shell/document logic lives in `src/client/server/`. - Client interactivity should stay in focused islands under `src/client/islands/`. - If a page needs client code, wire it through `src/client/entrypoints.ts` and `src/client/server/registry.tsx`. - Shared CSS starts at `src/client/styles.css`, which imports `src/client/styles/app.css`. ## Testing Notes - Worker Vitest uses `@cloudflare/vitest-pool-workers` and points at `src/worker/index.ts`. - Node unit tests use `vitest.unit.config.ts`. - The Vitest pool compatibility date should stay aligned with `wrangler.jsonc`. - Stable test env vars are defined in `test/vitest.bindings.ts`. ## Good Change Patterns - When adding a route, modify the owning module under `src/worker/routes/` and keep registration order safe. - When adding repo metadata, decide whether it belongs in DO storage or SQLite; if SQLite, add schema and DAL changes together. - When adding a new page, register it in `src/client/server/registry.tsx` and add a client entrypoint only if hydration is actually needed. - When changing pack or fetch behavior, look for existing worker tests before writing new code; the repo already has strong coverage for those paths. ## Avoid - Broad refactors across route, DO, and UI layers unless the task truly needs it - Raw SQL/Drizzle access outside the repo DB DAL - Accidental route shadowing with `/:owner` and `/:owner/:repo` - Re-introducing loose objects as a correctness dependency - Assuming README or docs reflect every current file path without checking the source tree