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.

186 lines · 9.6 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. Secrets use envelope encryption, and every sensitive
19 action goes to an append-only audit log.
20
21## How it fits on Cloudflare
22
23| Piece | Cloudflare product |
24|---|---|
25| HTTP, git smart HTTP, REST API, MCP, web UI | Workers (Hono) |
26| One repository: refs, objects, PRs, issues, reviews, checks, runs | Durable Object with SQLite (`RepoDO`), one per repository |
27| Large objects, Git LFS, logs, caches, artifacts, event archive | R2 |
28| 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) |
29| Webhooks, notifications, audit fan-out | Queues (with a dead-letter queue) |
30| Workflow runs (durable, survive restarts, 6 h job timeout) | Workflows |
31| Job runners and agent sandboxes | Containers (Ubuntu 24.04 image in `containers/runner`) |
32| Scheduled workflows (`on: schedule`) | Cron Triggers |
33| Live run logs and PR updates | Durable Object WebSockets (hibernation) |
34| Sign-in | Passkeys (WebAuthn) and sessions in D1. Cloudflare Access guards only `/auth/access`, the email recovery path |
35
36```
37apps/diffinery the Worker (src/index.ts), D1 migrations, wrangler config, e2e test
38apps/cli the `diffinery` command-line client
39containers/runner runner agent + sandbox server, and the container image
40packages/git-core git objects, packfiles, deltas, diff/merge (pure TypeScript)
41packages/workflow-yaml GitHub Actions workflow parser, expressions, matrix, cron
42packages/shared types shared by the Worker and the runner
43scripts bootstrap.sh, ensure-secrets.sh, render-config.mjs
44```
45
46## Deploying
47
48Deploys run from GitHub Actions on every push to `main` (`.github/workflows/deploy.yml`).
49The workflow needs two repository secrets:
50
51- `CLOUDFLARE_API_TOKEN`: account permissions Workers Scripts, D1, R2,
52 Queues, Workflows, Containers and Access: Apps and Policies (all Edit), and
53 zone permission Workers Routes Edit on `diffinery.com`.
54- `CLOUDFLARE_ACCOUNT_ID`
55
56On each deploy, `scripts/bootstrap.sh` creates anything that is missing: the D1
57database, R2 bucket, queues and one Access application that covers only
58`/auth/access` (email-based account recovery). It then applies D1 migrations. `wrangler deploy`
59builds and pushes the runner image. On the first deploy, `ensure-secrets.sh`
60generates `MASTER_KEY` and the OIDC signing key inside the job, so neither
61value is ever printed.
62
63If the token can't read your Zero Trust organization, set a repository
64**variable** `ACCESS_TEAM_DOMAIN` (for example `codician.cloudflareaccess.com`).
65
66Email recovery uses Access's email one-time PIN, which works without any setup.
67You can also add GitHub as a login method under Zero Trust → Settings → Authentication.
68
69## Signing in and inviting people
70
71Everyone signs in with a passkey. There are no passwords.
72
731. **First sign-in for admins.** The emails in `BOOTSTRAP_ADMINS` (in
74 `apps/diffinery/wrangler.jsonc`) become site admins. Open
75 `https://diffinery.com/auth/access`, confirm your email with the one-time
76 code, and add a passkey on the page that follows.
772. **Inviting someone.** An admin or org owner creates an invite from
78 *Settings → Site administration*, an organization's *People* tab, or
79 `diffinery invite friend@example.com --org codician`. The person opens the
80 link, picks a username and creates a passkey, and they're in.
813. **Lost passkey.** A site admin clicks *Sign-in link* next to the person in
82 *Settings → Site administration* and sends them the one-time link, which
83 works for 24 hours. People can also recover through `/auth/access` by email.
844. **More devices.** Add passkeys under *Settings → Passkeys and sessions*, or
85 keep one in a password manager that syncs. The same page signs out other
86 browsers.
875. Repository and org roles (read, triage, write, maintain, admin) decide what
88 each person can do.
89
90Nobody needs a Cloudflare account. Git and the CLI use a personal token from
91*Settings → Tokens*.
92
93```sh
94git clone https://diffinery.com/codician/website.git # username: anything, password: your dfy_ token
95diffinery login && diffinery auth setup-git # or let the CLI handle credentials
96```
97
98## Agents
99
100Create an agent under *Settings → Agents* or with `diffinery agent create reviewer-bot --org codician`.
101Then point any MCP client at it:
102
103```json
104{ "mcpServers": { "diffinery": { "type": "http", "url": "https://diffinery.com/mcp", "headers": { "Authorization": "Bearer dfy_…" } } } }
105```
106
107Tools include `get_file`, `list_tree`, `commit_files` (no git client needed),
108`create_branch`, `create_pull_request` (with an `intent` for reviewers),
109`review_pull_request`, `merge_pull_request` (policy-guarded), issues,
110`list_workflow_runs` and `get_job_logs`, `rerun_workflow`, and
111`sandbox_create`/`sandbox_exec` for running builds and tests.
112
113Agent guardrails apply everywhere: git, REST, MCP and the web UI.
114
115- An agent's role is capped at write, and never exceeds its owner's role.
116- Agents can't push to the default branch or protected branches, and can't
117 merge there. Turn this on per branch with `agentsMayPush` or `agentsMayMerge`.
118- A pull request opened by an agent needs at least one human approval, and
119 approvals from agents don't count toward it.
120- Agents can't change branch protection or read or set secrets.
121
122## Workflows
123
124Put workflow files in `.github/workflows/` (or `.diffinery/workflows/`).
125Supported triggers are `push`, `pull_request`, `workflow_dispatch` (with
126inputs), `schedule` and `workflow_run` (up to three levels deep, like GitHub),
127with branch, tag and path filters. Supported workflow features:
128
129- `needs` and job outputs, `if` with `success()`, `failure()`, `always()` and
130 `cancelled()`, `strategy.matrix` with include and exclude, `fail-fast` and
131 `max-parallel`, `concurrency` with `cancel-in-progress`, and `timeout-minutes`.
132- `continue-on-error`, `defaults.run`, `env` at every level, `$GITHUB_OUTPUT`,
133 `$GITHUB_ENV`, `$GITHUB_PATH`, `$GITHUB_STEP_SUMMARY`, and masking of
134 secrets and `::add-mask::` values.
135- `secrets.*`, `vars.*`, `GITHUB_TOKEN` (a per-job token), and
136 `permissions: id-token: write` for OIDC (issuer `https://diffinery.com`).
137- `runs-on: ubuntu-latest` (and any `ubuntu-*` or `linux` label) runs in a
138 Cloudflare Container. `runs-on: [self-hosted, …]` jobs wait for a runner you
139 start with `diffinery runner -R owner/repo --labels gpu,macos`. Use that for
140 macOS, GPUs or private networks.
141- Reusable workflows: `jobs.<id>.uses: ./.github/workflows/x.yml` from the same
142 commit, or `owner/repo/.github/workflows/x.yml@ref` from another repository
143 on this instance (public or internal ones, or private ones with the same
144 owner). `with`, `secrets` (a mapping or `inherit`), `on.workflow_call`
145 outputs and nesting up to four levels deep all work. A matrix on the calling
146 job isn't supported yet.
147- Docker, on hosted runners and on self-hosted runners with Docker installed:
148 a job `container:` (steps and JavaScript actions run inside it), `services:`
149 (reachable by name from the job container, or on `localhost` through their
150 mapped ports, with health checks awaited), Docker container actions
151 (`docker://image` steps and actions with `runs.using: docker`, built from
152 their Dockerfile), and plain `docker build` / `docker run` / `docker compose`
153 in steps. Hosted runners start the Docker daemon the first time a job uses
154 it and pull Docker Hub images through `mirror.gcr.io`.
155
156**Not supported yet:** `actions/github-script`,
157Windows and macOS hosted runners, `pull_request_target`, `merge_group`, and
158`deepen-not` / partial clone filters. Pull request runs use the head commit
159rather than a test merge commit. A job that uses an unsupported feature fails
160with a clear message instead of misbehaving.
161
162## Developing
163
164```sh
165pnpm install
166pnpm -r build && pnpm -r typecheck && pnpm -r test
167pnpm --filter @diffinery/app db:migrate:local
168pnpm dev # http://localhost:8787, signed in as dev@diffinery.local
169pnpm --filter @diffinery/app e2e # wrangler dev + real git + the runner agent + Chromium
170```
171
172The end-to-end test does the following:
173
1741. Pushes a repository with real `git`.
1752. Runs its CI through the actual runner agent.
1763. Checks secret scanning and branch protection.
1774. Has an agent open a pull request that needs a human approval and a green
178 check, then merges it.
1795. Clones shallow, then deepens the clone.
1806. Drives the MCP server.
1817. In Chromium with a virtual authenticator, joins from an invite with a new
182 passkey, adds and removes passkeys, signs out and in, and uses an admin
183 sign-in link.
184
185Team preferences for product and design decisions are in
186[docs/PREFERENCES.md](docs/PREFERENCES.md).