| 1 | # Contributing to g1t |
| 2 | |
| 3 | g1t is built the way it asks others to build: issues and pull requests on |
| 4 | [g1t.sh/flagon-io/g1t](https://g1t.sh/flagon-io/g1t), required checks that prove |
| 5 | a change works, and a merge queue that keeps `main` passing. |
| 6 | |
| 7 | ## A change ships with its docs |
| 8 | |
| 9 | Documentation is part of the product, held to the same bar as the code. |
| 10 | A pull request that changes what someone can do, see or call changes the |
| 11 | docs in the same pull request: |
| 12 | |
| 13 | | If you change | Update | |
| 14 | | --- | --- | |
| 15 | | Something a person does on g1t.sh | The guide for it in `apps/docs/src/content/docs/guides/` | |
| 16 | | An API route, a field, or an MCP tool | `apps/api/src/operations.rs` descriptions (they feed the OpenAPI document and the API reference), the example and notes for it in `apps/api/src/reference.json`, `reference/mcp.md`, and any guide that shows the call. Then refresh the docs' copy of the OpenAPI document with `G1T_WRITE_OPENAPI=1 cargo test -p g1t-api openapi`; `cargo test` fails until you do. | |
| 17 | | How agents behave | `guides/working-with-g1t.md`, and `apps/web/public/llms.txt` | |
| 18 | | Settings, limits or prices | The page that names them, and the table it is in | |
| 19 | | A new feature | A section in the guide that owns it, linked from the docs home if it is a new task | |
| 20 | |
| 21 | The style, in short: plain sentences, second person, no marketing words, |
| 22 | sentence-case headings, a numbered list for steps, a table for options, and |
| 23 | every API mention with its exact route or tool name. Examples are copyable |
| 24 | and real. Nothing is documented that the code does not do. |
| 25 | |
| 26 | Check the docs build before you push: |
| 27 | |
| 28 | ```sh |
| 29 | cd apps/docs && npm run build |
| 30 | ``` |
| 31 | |
| 32 | ## Icons |
| 33 | |
| 34 | - **Interface icons come from [Lucide](https://lucide.dev/icons).** In the |
| 35 | apps, import them from `lucide-react`. In the docs, use `@lucide/astro`: |
| 36 | import an icon component, or give `apps/docs/src/components/Card.astro` |
| 37 | and `Aside.astro` a Lucide name such as `icon="git-branch"`. Starlight's |
| 38 | own `<Card>`, `<LinkCard>` and `<Aside>` draw Starlight's icon set, so the |
| 39 | docs import the wrappers in `apps/docs/src/components/` instead. |
| 40 | - **Brand marks come from [Simple Icons](https://simpleicons.org)** |
| 41 | (`simple-icons`), and follow each brand's own usage guidelines. Add the |
| 42 | dependency with the first one you show. |
| 43 | - **The g1t logo and illustrations are our own artwork**, in |
| 44 | `apps/web/app/components/logo.tsx` and `art.tsx`. |
| 45 | - Never hand-copy an icon's SVG paths into a file. If Lucide lacks the |
| 46 | icon you need, pick the closest one that it has. |
| 47 | |
| 48 | ## Before you push |
| 49 | |
| 50 | - `cargo test` in the crate or service you changed. |
| 51 | - `npx tsc -b --force` in `apps/web` (the incremental build misses changes |
| 52 | in `packages/contracts`). |
| 53 | - Look at what you changed in a browser. Screenshots catch what type |
| 54 | checks do not. |
| 55 | |
| 56 | ## Deploying |
| 57 | |
| 58 | Pushes to `main` deploy themselves: `.g1t/workflows/deploy.yml` runs |
| 59 | `scripts/deploy.mjs`, which deploys only the parts that changed, migrations |
| 60 | first. Every deployable part is listed in `deploy/stack.jsonc`; a new service |
| 61 | or app goes there, and in the table on |
| 62 | [How a self-hosted g1t runs](https://docs.g1t.sh/guides/self-hosting-architecture/#each-part) |
| 63 | (`npm run test:deploy` says what is missing). |
| 64 | [Deploy g1t to Cloudflare](https://docs.g1t.sh/guides/deploy-to-cloudflare/) |
| 65 | covers the tool, the workflow, rollbacks, adding a unit and first-time |
| 66 | setup. |
| 67 | |
| 68 | - **Migrations run before the code**, so the old code reads the new schema |
| 69 | for a minute or more. Add tables and columns; change what rows mean in |
| 70 | two deploys (code that reads both forms first); never drop what live code |
| 71 | still reads. |
| 72 | - **The self-hosted runner** is released, not deployed: bump `version` in |
| 73 | `crates/runner/Cargo.toml`, merge, and push a `runner-v<version>` tag. |
| 74 | `.g1t/workflows/runner-release.yml` builds, signs and publishes it, and |
| 75 | runners update themselves to it. `scripts/runner-release.mjs` does the |
| 76 | same by hand. |
| 77 | |
| 78 | ## Speed |
| 79 | |
| 80 | Pages are a few rounds of service calls, and each round costs a trip to |
| 81 | the databases, so start calls together and add no rounds. |
| 82 | |
| 83 | - Every page answers with `Server-Timing`: each loader, each service's |
| 84 | calls and their database time. Look at it in DevTools when a page feels |
| 85 | slow. |
| 86 | - `scripts/perf/measure.ps1` times pages from your machine (`-BrowserUA` |
| 87 | for signed-out pages as a browser sees them). A page that only reads |
| 88 | must not set the `g1t_d1` cookie: a new read-only service method goes in |
| 89 | `READS` in `apps/web/app/lib/perf.ts`. |
| 90 | - Targets from the US: signed-out pages under 200 ms to first byte, |
| 91 | signed-in pages under 400 ms, streamed panels within a second. |
| 92 | - Workers run without Smart Placement; `scripts/perf/placement-probe.mjs` |
| 93 | measures a placement before you pin one. |
| 94 | |
| 95 | ## Rate limits |
| 96 | |
| 97 | `RATE_LIMITS` in `packages/contracts/src/rate-limits.ts` is the table of |
| 98 | record, and the [rate limits page](https://docs.g1t.sh/reference/rate-limits/) |
| 99 | is the public one: change both, and the binding in the Worker's |
| 100 | `wrangler.jsonc`, together (`front-door-limits.test.ts` fails when they |
| 101 | disagree). Limits fail open, keys hash anything secret, and each Worker |
| 102 | takes its namespace ids from its own block of a hundred (41xx packages, |
| 103 | 42xx web, 43xx repos, 44xx api, 45xx og, 46xx status). |
| 104 | |
| 105 | ## Operating g1t.sh |
| 106 | |
| 107 | For Flagon staff. |
| 108 | |
| 109 | - **Incidents** are declared, updated and resolved in sudo, under |
| 110 | **Platform → Incidents**, and appear on status.g1t.sh within 30 seconds. |
| 111 | Declare as soon as people are affected, and pick the higher severity |
| 112 | when unsure. SEV1 (down for most people, or data at risk) gets an update |
| 113 | at least every 30 minutes and SEV2 (a core part broken for many) at least |
| 114 | hourly; both email subscribers and need a blameless postmortem within |
| 115 | five working days. |
| 116 | - **An Artifacts outage:** `scripts/ops/gitstore-namespaces.mjs` shows a |
| 117 | failing namespace. After 15 minutes, serve it read-only from the fallback |
| 118 | git store (`scripts/ops/restore-to-gitstore.mjs restore`, then the repos |
| 119 | Worker's secret `GIT_FALLBACK_NAMESPACES`) and open an incident. To |
| 120 | switch back, `reconcile` anything the fallback took, then delete the |
| 121 | secret. The script's header has every step. |
| 122 | - **Costs and margin**, model prices, credits and the platform pause are |
| 123 | in sudo, under **Costs & margin**. The code is in `services/billing/src` |
| 124 | (`costs.rs`, `margin.rs`, `pricing.rs`, `budget.rs`, `platform.rs`). |