Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

227 lines · 12.1 KBRaw History
1# Diffinery
2
3A git forge for teams of people and AI agents, running entirely on Cloudflare.
4Git hosting, pull requests and reviews, issues, GitHub-Actions-compatible CI,
5an MCP server for agents, and sandboxes, with no servers to run.
6
7- **Drop-in for GitHub workflows.** `.github/workflows/*.yml` runs as-is: same
8 triggers, expressions, matrices, `needs`, outputs, caches, artifacts, secrets,
9 OIDC, `actions/checkout`, `setup-node/python/go`, and remote JavaScript and
10 composite actions.
11- **Built for agents.** Agents are first-class accounts owned by a person or
12 team, with their own tokens and limits. They can't push or merge to protected
13 branches, and their pull requests always need a human approval. Every commit
14 they make is stamped with who they act for. They work through MCP
15 (`/mcp`) or the REST API, and can start sandboxes to build and test.
16- **Secure by default.** People sign in with passkeys, with no passwords to
17 leak or phish. Invites, roles and branch protection decide what they can do. Pushes are scanned for
18 leaked credentials and blocked if they contain any; nothing from a blocked
19 push is kept. A repository admin (a person, never an agent) can push anyway
20 with `git push -o allow-secrets=<reason>`, which is audited. A file can opt
21 out with a `diffinery:allow-secret` comment, for test fixtures. Secrets use
22 envelope encryption, and every sensitive action goes to an append-only audit log.
23
24## How it fits on Cloudflare
25
26| Piece | Cloudflare product |
27|---|---|
28| HTTP, git smart HTTP, REST API, MCP, web UI | Workers (Hono) |
29| One repository: refs, objects, PRs, issues, reviews, checks, runs | Durable Object with SQLite (`RepoDO`), one per repository |
30| Large objects, Git LFS, logs, caches, artifacts, event archive | R2 |
31| Accounts, orgs, tokens, invites, notifications, audit log | D1, read from nearby replicas through the Sessions API (each browser keeps a bookmark so it reads its own writes) |
32| Webhooks, notifications, audit fan-out | Queues (with a dead-letter queue) |
33| Workflow runs (durable, survive restarts, 6 h job timeout) | Workflows |
34| Job runners and agent sandboxes | Containers (Ubuntu 24.04 image in `containers/runner`) |
35| Scheduled workflows (`on: schedule`) | Cron Triggers |
36| Live run logs and PR updates | Durable Object WebSockets (hibernation) |
37| Sign-in | Passkeys (WebAuthn) and sessions in D1. Cloudflare Access guards only `/auth/access`, the email recovery path |
38
39```
40apps/diffinery the Worker (src/index.ts), D1 migrations, wrangler config, e2e test
41apps/cli the `diffinery` command-line client
42containers/runner runner agent + sandbox server, and the container image
43packages/git-core git objects, packfiles, deltas, diff/merge (pure TypeScript)
44packages/workflow-yaml GitHub Actions workflow parser, expressions, matrix, cron
45packages/shared types shared by the Worker and the runner
46scripts bootstrap.sh, ensure-secrets.sh, render-config.mjs
47```
48
49## Deploying
50
51Diffinery deploys itself: this repository lives at
52`diffinery.com/codician-team/diffinery`, and every push to `main` there runs
53`.github/workflows/deploy.yml` on Diffinery's hosted runners, followed by the
54live test (`.github/workflows/live-test.yml`). GitHub stays the upstream for
55now: `.github/workflows/sync-to-diffinery.yml` copies every branch and tag, and
56the secrets and variables below, to Diffinery on each push, then waits for the
57Actions runs there and fails if any of them fail. Running Deploy by hand on
58GitHub still deploys, as a fallback.
59
60The workflow needs two repository secrets:
61
62- `CLOUDFLARE_API_TOKEN`: account permissions Workers Scripts, D1, R2,
63 Queues, Workflows, Containers and Access: Apps and Policies (all Edit), and
64 zone permission Workers Routes Edit on `diffinery.com`.
65- `CLOUDFLARE_ACCOUNT_ID`
66
67On each deploy, `scripts/bootstrap.sh` creates anything that is missing: the D1
68database, R2 bucket, queues and one Access application that covers only
69`/auth/access` (email-based account recovery). It then applies D1 migrations. `wrangler deploy`
70builds and pushes the runner image. On the first deploy, `ensure-secrets.sh`
71generates `MASTER_KEY` and the OIDC signing key inside the job, so neither
72value is ever printed.
73
74If the token can't read your Zero Trust organization, set a repository
75**variable** `ACCESS_TEAM_DOMAIN` (for example `codician.cloudflareaccess.com`).
76
77Email recovery uses Access's email one-time PIN, which works without any setup.
78You can also add GitHub as a login method under Zero Trust → Settings → Authentication.
79
80## Signing in and inviting people
81
82Everyone signs in with a passkey. There are no passwords.
83
841. **First sign-in for admins.** The emails in `BOOTSTRAP_ADMINS` (in
85 `apps/diffinery/wrangler.jsonc`) become site admins. Open
86 `https://diffinery.com/auth/access`, confirm your email with the one-time
87 code, and add a passkey on the page that follows.
882. **Inviting someone.** An admin or org owner creates an invite from
89 *Settings → Site administration*, an organization's *People* tab, or
90 `diffinery invite friend@example.com --org codician`. The person opens the
91 link, picks a username and creates a passkey, and they're in.
923. **Lost passkey.** A site admin clicks *Sign-in link* next to the person in
93 *Settings → Site administration* and sends them the one-time link, which
94 works for 24 hours. People can also recover through `/auth/access` by email.
954. **More devices.** Add passkeys under *Settings → Passkeys and sessions*, or
96 keep one in a password manager that syncs. The same page signs out other
97 browsers.
985. Repository and org roles (read, triage, write, maintain, admin) decide what
99 each person can do.
100
101Nobody needs a Cloudflare account. Git and the CLI use a personal token from
102*Settings → Tokens*.
103
104```sh
105git clone https://diffinery.com/codician/website.git # username: anything, password: your dfy_ token
106diffinery login && diffinery auth setup-git # or let the CLI handle credentials
107```
108
109## Agents
110
111Create an agent under *Settings → Agents* or with `diffinery agent create reviewer-bot --org codician`.
112Then point any MCP client at it:
113
114```json
115{ "mcpServers": { "diffinery": { "type": "http", "url": "https://diffinery.com/mcp", "headers": { "Authorization": "Bearer dfy_…" } } } }
116```
117
118Tools include `get_file`, `list_tree`, `commit_files` (no git client needed),
119`create_branch`, `create_pull_request` (with an `intent` for reviewers),
120`review_pull_request`, `merge_pull_request` (policy-guarded), issues,
121`list_workflow_runs` and `get_job_logs`, `rerun_workflow`, and
122`sandbox_create`/`sandbox_exec` for running builds and tests.
123
124Agent guardrails apply everywhere: git, REST, MCP and the web UI.
125
126- An agent's role is capped at write, and never exceeds its owner's role.
127- Agents can't push to the default branch or protected branches, and can't
128 merge there. Turn this on per branch with `agentsMayPush` or `agentsMayMerge`.
129- A pull request opened by an agent needs at least one human approval, and
130 approvals from agents don't count toward it.
131- Agents can't change branch protection or read or set secrets.
132
133## Workflows
134
135Put workflow files in `.github/workflows/` (or `.diffinery/workflows/`).
136Supported triggers are `push`, `pull_request`, `pull_request_target`,
137`merge_group`, `workflow_dispatch` (with inputs), `schedule` and `workflow_run`
138(up to three levels deep, like GitHub), with branch, tag and path filters.
139
140Pull requests behave as on GitHub:
141
142- `pull_request` runs check out the test merge commit, `refs/pull/<n>/merge`
143 (the head merged into the current base), with `GITHUB_REF` set to that ref.
144 While a pull request conflicts with its base, its `pull_request` workflows
145 don't run. `refs/pull/<n>/head` and `refs/pull/<n>/merge` can be fetched but
146 not pushed.
147- `pull_request_target` runs the base branch's own workflow on the base commit,
148 with the repository's secrets, so a pull request can't change what runs.
149 Check out the pull request's code yourself only if you trust it.
150- Both report their checks on the pull request's head commit. `opened`,
151 `synchronize`, `reopened` and `closed` (with `pull_request.merged`) fire.
152- Merge queue: turn on **Require a merge queue** in a branch protection rule
153 (or `"mergeQueue": {"method": "squash"}` through the API). "Merge when ready"
154 then adds a pull request to the queue. Each entry gets a merge group branch,
155 `gh-readonly-queue/<base>/pr-<n>-<head sha>`, holding the base, every pull
156 request ahead of it and its own changes, and `merge_group` (and `push`)
157 workflows run on it. Once its required checks pass and the entries ahead have
158 merged, the base fast-forwards to exactly the tested commit. A group that
159 fails, conflicts or exceeds the check timeout leaves the queue with a comment
160 saying why, and the groups behind it are rebuilt. The queue is at
161 `/<owner>/<repo>/queue/<branch>`; the API is
162 `PUT`/`DELETE /repos/{owner}/{repo}/pulls/{n}/merge-queue` and
163 `GET /repos/{owner}/{repo}/merge-queue/{branch}`, `diffinery pr merge` queues
164 automatically, and the MCP `merge_pull_request` tool does too.
165
166Supported workflow features:
167
168- `needs` and job outputs, `if` with `success()`, `failure()`, `always()` and
169 `cancelled()`, `strategy.matrix` with include and exclude, `fail-fast` and
170 `max-parallel`, `concurrency` with `cancel-in-progress`, and `timeout-minutes`.
171- `continue-on-error`, `defaults.run`, `env` at every level, `$GITHUB_OUTPUT`,
172 `$GITHUB_ENV`, `$GITHUB_PATH`, `$GITHUB_STEP_SUMMARY`, and masking of
173 secrets and `::add-mask::` values.
174- `secrets.*`, `vars.*`, `GITHUB_TOKEN` (a per-job token), and
175 `permissions: id-token: write` for OIDC (issuer `https://diffinery.com`).
176- `runs-on: ubuntu-latest` (and any `ubuntu-*` or `linux` label) runs in a
177 Cloudflare Container. `runs-on: [self-hosted, …]` jobs wait for a runner you
178 start with `diffinery runner -R owner/repo --labels gpu,macos`. Use that for
179 macOS, GPUs or private networks.
180- Reusable workflows: `jobs.<id>.uses: ./.github/workflows/x.yml` from the same
181 commit, or `owner/repo/.github/workflows/x.yml@ref` from another repository
182 on this instance (public or internal ones, or private ones with the same
183 owner). `with`, `secrets` (a mapping or `inherit`), `on.workflow_call`
184 outputs and nesting up to four levels deep all work. A matrix on the calling
185 job isn't supported yet.
186- Docker, on hosted runners and on self-hosted runners with Docker installed:
187 a job `container:` (steps and JavaScript actions run inside it), `services:`
188 (reachable by name from the job container, or on `localhost` through their
189 mapped ports, with health checks awaited), Docker container actions
190 (`docker://image` steps and actions with `runs.using: docker`, built from
191 their Dockerfile), and plain `docker build` / `docker run` / `docker compose`
192 in steps. Hosted runners start the Docker daemon the first time a job uses
193 it and pull Docker Hub images through `mirror.gcr.io`.
194
195**Not supported yet:** `actions/github-script`,
196Windows and macOS hosted runners, and
197`deepen-not` / partial clone filters. A job that uses an unsupported feature fails
198with a clear message instead of misbehaving.
199
200## Developing
201
202```sh
203pnpm install
204pnpm -r build && pnpm -r typecheck && pnpm -r test
205pnpm --filter @diffinery/app db:migrate:local
206pnpm dev # http://localhost:8787, signed in as dev@diffinery.local
207pnpm --filter @diffinery/app e2e # wrangler dev + real git + the runner agent + Chromium
208```
209
210The end-to-end test does the following:
211
2121. Pushes a repository with real `git`.
2132. Runs its CI through the actual runner agent.
2143. Checks secret scanning and branch protection.
2154. Has an agent open a pull request that needs a human approval and a green
216 check, then merges it.
2175. Runs pull request workflows on test merge commits, `pull_request_target`
218 from the base branch, and a merge queue with `merge_group` runs, including a
219 group that fails and leaves the queue.
2206. Clones shallow, then deepens the clone.
2217. Drives the MCP server.
2228. In Chromium with a virtual authenticator, joins from an invite with a new
223 passkey, adds and removes passkeys, signs out and in, and uses an admin
224 sign-in link.
225
226Team preferences for product and design decisions are in
227[docs/PREFERENCES.md](docs/PREFERENCES.md).