01 what forge is
Your forge, on your own server
forge keeps your repositories on your own server and publishes them as a static site: every branch, file and commit, the reviews on them, the CI result of every commit, and each repo's sites and releases. The pages work without JavaScript and update while you read them.
Changes go through git and ssh. You push code, reviews, sites and releases; you run an ssh command to make a repo, land a review or invite someone. On the web you read. Sign in with a passkey to comment and approve. A push that adds a password or a key is refused.
| you | with | the forge shows |
|---|---|---|
| push code | git push | a page for every branch, directory, file and commit |
| push a branch | forge push | a review, with a page for each revision |
| run CI | forge attest | a signed pass or fail on each commit |
| publish a site | forge site deploy | the site on its own address, and a preview per branch |
| cut a release | forge release publish | downloads with signed checksums |
| invite someone | ssh git@src.rest invite | a one-time link to join, as a collaborator or reader |
The examples use src.rest, where forge hosts itself as cat/forge. cat runs it; fox is another user.
02 browse
Every branch, file, commit and review is a page
URLs are paths: /cat/greet/tree/main/greet.sh/ is that file on main, and every branch has the same pages. Markdown is rendered and code is highlighted. The home page lists people, recent activity and repos by topic.
| page | what is on it |
|---|---|
/cat/greet/ | open reviews, recent commits, the README |
tree/<branch>/, blob/<branch>/<path>/ | files and directories on any branch |
log/<branch>/, commit/<sha>/ | history, each commit with its diff and CI result |
reviews/, reviews/<id>/ | reviews, as stacks, and each review's diffs and discussion |
wiki/, sites/, releases/ | the wiki from wiki/, deployed sites and previews, downloads |
insights/ | pushes, reviews and CI results over time, charted |
atom.xml | a feed of the newest commits on main |
- Search. / searches a repo's commits, reviews, wiki, paths and file contents; t finds a file on the current branch.
- The keyboard. j k move through a list, n N through a review's commits, [ ] up and down a stack, v marks a file viewed. ? lists them all.
- Live. An open page updates itself after a push, without losing your place.
- Feeds. Every repo has an Atom feed of its newest commits.
- Diagrams.
```mermaidfences in any markdown file are drawn as diagrams, without JavaScript. - Blame and history. A file on
mainshows who last changed each line, and every commit that changed it with the review it landed in.
03 reviews
Push a branch and it is a review
Any branch other than main is a review once pushed: its commits' diffs in order, plus a combined diff. A branch made from another review's branch stacks on top of it.
$ git switch -c cat/flags $ forge push -t "parse --name" -d "Flags, parsed by hand" remote: review #2: https://src.rest/cat/greet/reviews/2/
6771779README: document flagscat/helpdff8bcaadd --help
#3 Usage and --helplayer 2, on cat/flags
80b9ddaadd --shoutcat/flagsafd124bparse --name
#2 parse --namelayer 1, on main
895bc48README: be politemain
- Revisions. Each push is a revision. The page shows what changed since the last one, commit by commit, even after a rebase.
- Stacks.
forge push --stackpushes every layer, lowest first; [ and ] move between layers on the page. - Status. Open, draft (
--draft, or awip/branch), merged once it is inmain, closed when its branch is deleted. - Reading. Files fold, can be marked viewed and stay viewed until they change.
1: 4146e8e ! 1: afd124b parse --name @@ tests.txt + ## wiki/Design.md ## - Flags are parsed by hand; no getopt. - Output goes to stdout, errors to stderr. ++- `--name` takes the next argument verbatim. 2: ff819c4 = 2: 80b9dda add --shout 3: d70bd83 = 3: dff8bca add --help 4: ef1d881 ! 4: 6771779 README: document flags -+ --shout uppercase the greeting ++ --shout UPPERCASE the greeting
= unchanged, ! changedComments and approvals
Signed in, you comment and approve at the end of a review page, or comment on a line with the + in the combined diff. From the terminal:
$ forge comment 2 greet.sh:12 -m "Quote the name?" $ forge approve 2
An approval is for the code as it is: rewording a commit keeps it, changing the code marks it stale. You can't approve your own review. After a new revision, older comments are marked outdated and keep the line they quoted. A repo can require someone else's approval before main moves.
Approval rules
How much approval, and from whom, can depend on what a change touches. .forge/approvals on main gives each path a rule; a change needs every rule its files take. The rules are read from main as it was, so a review can't loosen its own.
# paths rule
.forge/ bin/forge 2
lib/identity.ts 1 from fox
lib/ 1 + agent
*.md wiki/ none
* 1
A review page starts with what landing needs: CI, each rule and who has approved, and the latest agent review.
Agent reviews
forge critique runs the reviewer main names, .forge/review, over a review on your machine: Claude Code, Codex or a script of your own. It gets the diff and the project's rules in .forge/review.md. Its findings become comments marked agent, some blocking, and it signs what is still open into a git note, as CI does. The next run looks only at what changed since and carries the open findings forward until the reviewer, or you with --resolve, says they are dealt with. A rule's + agent wants one with nothing blocking.
Landing
forge land checks CI (and approval, if required), fast-forwards main and deletes the landed branches. Over ssh the forge does it itself, no clone needed:
$ forge land 2 $ ssh git@src.rest land cat/greet 2
main only fast-forwards, so rebase a review onto main before landing; git rebase -i --update-refs moves every layer of a stack with it.
04 ci
CI runs on your machine and is signed
forge attest runs .forge/ci on your machine, on a clean checkout of each commit. It signs the result with your SSH key. forge push uploads the results, and every commit, review and branch shows pass or fail and who signed it. Failures are shown too.
forge-attestation 1 type ci commit ff819c459d6f222c... tree 69a735fe3cc0a8a8...1 script .forge/ci 99a0d5ea2eee...2 result pass3 exit 0 started 2026-10-18T02:00:14Z duration 0s runner cat@laptop4 system Darwin 25.5.0 arm64 env host log sha256:6cc79019... 4 lines |ok greet --name fox |ok greet --shout -----BEGIN SSH SIGNATURE-----5 U1NIU0lHAAAAAQAAADMAAAALc3No...
- The files, not the commit. A reworded or reordered commit with the same files keeps its result, and
forge attestskips what already passed unless you pass--force. - The script, by hash: change
.forge/ciand it is a different claim. - The result and exit code.
- The runner as the signer describes it: user, machine and system, plus the Nix lock file if there is one.
- The signature over everything above. Log lines start with
|, so output can't fake a field.
$ forge attest --stack # every commit on top of main $ forge verify # each commit's results, and who signed them
What a verified pass proves
- Someone with a key on this forge claims these exact files passed this exact script, on a machine they describe.
- Nobody edited it since: turning
result failintoresult passbreaks the signature. - Who signed it.
What it doesn't
- That the person is honest. Someone can sign a pass for tests they never ran.
- That the machine was clean or the setup reproducible, unless Nix pinned it.
- Anything about commits nobody tested.
To make it stronger, keep the key in hardware: a Secure Enclave key through Secretive, or a FIDO sk- key. Add a flake.nix so CI runs under nix develop. Have a second person run forge attest too; results from several people add up.
A repo can require a verified pass before main moves (for the new tip or every new commit) and for a release's tag. Pushes and lands without one are refused.
CI can also report numbers about a commit, a line each to $FORGE_METRICS: bundle_kb 412 KB, tests 318. They are signed with the result, and the repo's insights chart each one commit by commit on main.
05 sites and releases
Sites and releases ship with the repo
Any repo can publish static sites: docs, a Docusaurus build, a small app. List each in .forge/sites with its output directory and build command, then deploy:
# .forge/sites: name, output, build command (- for none) docs build npm ci && npm run build $ forge site deploy docs
On main this publishes the site; on any other branch, a preview of that branch, linked from its review. Deleting the branch removes its previews. Every deploy is kept, so you can roll back. This guide is a site of cat/forge.
Each site gets its own hostname, so sites can't read each other's cookies or storage:
| deploy | address |
|---|---|
cat/greet, site docs | greet-docs |
fox/dotfiles, site home | dotfiles-home-fox |
docs on the branch cat/flags | greet-docs--cat-flags |
cat/forge, site guide | forge-guide, this page |
The forge's owner gets the short names. Sites are served like Cloudflare Pages: _headers and _redirects work, /about finds about.html, a missing page gets the nearest 404.html and previews aren't indexed.
Releases
A release is a tag's files plus a SHA256SUMS signed with your key, bound to the repo and tag. The releases page shows who signed, and anyone can verify a download:
$ forge release publish v1.0 greet.sh greet-1.0.tar.gz $ forge release verify --url https://src.rest/cat/greet/releases/download/v1.0/ greet.sh ok greet.sh: matches SHA256SUMS of cat/greet v1.0, signed by cat
Downloads keep stable links: /cat/ is always the newest. A private repo's releases are for its readers only.
06 people
Who can do what
A user is a name and an SSH key; the same key signs their CI results, deploys and releases. An admin invites someone with a link:
$ ssh git@src.rest invite fox --repo cat/012 --as collaborator https://src.rest/login/#invite=...
The link works once, for a week: fox opens it, pastes a public key and can add a passkey. Each repo's owner decides who else works on it:
$ ssh git@src.rest collaborators cat/012 add fox # may push anything $ ssh git@src.rest readers cat/012 add fox # may read it while it is private $ ssh git@src.rest who cat/012 owner cat collaborator fox
cat/012| owner, collaborators | readers, and any user of a public repo | anyone | |
|---|---|---|---|
| read pages, clone | yes | yes | a public repo |
| comment, approve | yes | yes | no |
| push a review, a preview, CI results | yes | under their own name, fox/... | no |
move main, land, tag, deploy, release | yes | no | no |
| make it private or public, choose readers | yes | no | no |
| choose collaborators, remove it | the owner | no | no |
Private repos
A private repo is visible only to its owner, collaborators and readers: over ssh, on the web when signed in and over HTTPS with a token. To everyone else it doesn't exist.
$ ssh git@src.rest new --private notes $ ssh git@src.rest private cat/012 # or public, after a check of its whole history for secrets
Passkeys and tokens
There are no passwords. Passkeys and tokens are made over ssh; lose one and make another.
$ ssh git@src.rest passkey # a one-time link that adds a passkey $ ssh git@src.rest token --repo cat/012 # for curl and HTTPS clones, shown once $ ssh git@src.rest sessions # where you are signed in; rm signs one out
07 day to day
Day to day
once
- git config --global forge.signingkey ~/.ssh/id_ed25519.pub
- The key that signs CI results and deploys; a
.pubpath signs through ssh-agent. - ssh git@src.rest signers > ~/.config/forge/allowed_signers
- Then point
forge.allowedsignersat it, soforge verifycan check signatures. - ssh git@src.rest new greet "say hello"
- Make a repo under your name; prints the remote to add.
--privatemakes it private. - git push git@src.rest:cat/greet.git main
- Or just push: the repo is created if it doesn't exist.
- forge import https://github.com/you/thing.git
- Copy a repo in (set
forge.hostfirst).--branchestakes every branch.
a review
- git switch -c cat/topic
- Branch under your name; once pushed, it is a review.
- forge push -t "Title"
- Push the branch and the CI results.
--draft,--ready,--target <branch>,-dfor a description. - forge push --stack
- Push every layer of a stack, lowest first.
- forge reviews
- Open and draft reviews;
forge checkout <id>fetches one. - forge comment [<id>] [path:line] -m "..."
- Comment on a review, or on one line of it. Without
-m, reads stdin or opens$EDITOR. - forge approve [<id>]
- Approve the review as it is now.
- forge critique [<id>]
- An agent review: run
main's.forge/review, post its findings, sign what is open.--full,--fix,--resolve <id>. - forge land [<id>]
- Check CI and the approval rules, move
mainforward, delete the landed branches. - ssh git@src.rest land cat/greet 7
- Have the forge land review 7, no clone needed, with the same checks.
forge land --serverdoes it from a clone.
ci
- forge attest [--stack] [rev...]
- Run and sign CI for commits (default
HEAD;--stack, everything on top ofmain).--forcereruns what passed. - forge verify [rev...]
- Show each commit's results and who signed them.
sites
- forge site deploy [name...]
- Build and publish from a clean checkout of
HEAD: the site onmain, a preview elsewhere.--herebuilds in your work tree. - forge site push <dir> <name>
- Publish a directory you built yourself.
- forge site url <name> [branch]
- Where a site or preview is served.
releases
- forge release publish <tag> <file>...
- The tag's files with signed checksums, pushed with the tag and your CI results; again to replace them.
- forge release verify <file>...
- Check downloads against their checksums, and the signature against the forge's keys.
- forge release ls, rm <tag>
- This repo's releases; delete one.
people, over ssh
- ssh git@src.rest help
- Lists the commands you can run.
- ssh git@src.rest invite fox --repo cat/012 --as collaborator
- Admins only: a one-time invite link.
inviteslists pending ones. - ssh git@src.rest collaborators cat/012 add fox
- Or
readers;rmtakes it back, and no argument lists them. - ssh git@src.rest who cat/012
- Its owner, collaborators and readers.
- ssh git@src.rest ls
- Your repos, and the private ones you may read.
- ssh git@src.rest passkey
- A link that adds a passkey;
tokenfor curl and HTTPS clones.
08 not yet
What isn't there
- Issues. No bug or task tracker yet.
- Notifications. No email or push notifications; open pages update themselves.
- CI on the server. By design: CI runs on your machine, and the forge checks signatures.