diffinery/README.md
| 1 | # Diffinery |
| 2 | |
| 3 | A git forge for teams of people and AI agents, running entirely on Cloudflare. |
| 4 | Git hosting, pull requests and reviews, issues, GitHub-Actions-compatible CI, |
| 5 | an 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 | |
| 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 | ``` |
| 37 | apps/diffinery the Worker (src/index.ts), D1 migrations, wrangler config, e2e test |
| 38 | apps/cli the `diffinery` command-line client |
| 39 | containers/runner runner agent + sandbox server, and the container image |
| 40 | packages/git-core git objects, packfiles, deltas, diff/merge (pure TypeScript) |
| 41 | packages/workflow-yaml GitHub Actions workflow parser, expressions, matrix, cron |
| 42 | packages/shared types shared by the Worker and the runner |
| 43 | scripts bootstrap.sh, ensure-secrets.sh, render-config.mjs |
| 44 | ``` |
| 45 | |
| 46 | ## Deploying |
| 47 | |
| 48 | Deploys run from GitHub Actions on every push to `main` (`.github/workflows/deploy.yml`). |
| 49 | The 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 | |
| 56 | On each deploy, `scripts/bootstrap.sh` creates anything that is missing: the D1 |
| 57 | database, R2 bucket, queues and one Access application that covers only |
| 58 | `/auth/access` (email-based account recovery). It then applies D1 migrations. `wrangler deploy` |
| 59 | builds and pushes the runner image. On the first deploy, `ensure-secrets.sh` |
| 60 | generates `MASTER_KEY` and the OIDC signing key inside the job, so neither |
| 61 | value is ever printed. |
| 62 | |
| 63 | If the token can't read your Zero Trust organization, set a repository |
| 64 | **variable** `ACCESS_TEAM_DOMAIN` (for example `codician.cloudflareaccess.com`). |
| 65 | |
| 66 | Email recovery uses Access's email one-time PIN, which works without any setup. |
| 67 | You can also add GitHub as a login method under Zero Trust → Settings → Authentication. |
| 68 | |
| 69 | ## Signing in and inviting people |
| 70 | |
| 71 | Everyone signs in with a passkey. There are no passwords. |
| 72 | |
| 73 | 1. **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. |
| 77 | 2. **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. |
| 81 | 3. **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. |
| 84 | 4. **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. |
| 87 | 5. Repository and org roles (read, triage, write, maintain, admin) decide what |
| 88 | each person can do. |
| 89 | |
| 90 | Nobody needs a Cloudflare account. Git and the CLI use a personal token from |
| 91 | *Settings → Tokens*. |
| 92 | |
| 93 | ```sh |
| 94 | git clone https://diffinery.com/codician/website.git # username: anything, password: your dfy_ token |
| 95 | diffinery login && diffinery auth setup-git # or let the CLI handle credentials |
| 96 | ``` |
| 97 | |
| 98 | ## Agents |
| 99 | |
| 100 | Create an agent under *Settings → Agents* or with `diffinery agent create reviewer-bot --org codician`. |
| 101 | Then 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 | |
| 107 | Tools 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 | |
| 113 | Agent 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 | |
| 124 | Put workflow files in `.github/workflows/` (or `.diffinery/workflows/`). |
| 125 | Supported triggers are `push`, `pull_request`, `workflow_dispatch` (with |
| 126 | inputs) and `schedule`, with branch, tag and path filters. Supported workflow |
| 127 | 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 | |
| 148 | **Not supported yet:** job `container:` and `services:`, Docker container actions, `actions/github-script`, |
| 149 | Windows and macOS hosted runners, `pull_request_target`, `merge_group`, and |
| 150 | `deepen-not` / partial clone filters. Pull request runs use the head commit |
| 151 | rather than a test merge commit. A job that uses an unsupported feature fails |
| 152 | with a clear message instead of misbehaving. |
| 153 | |
| 154 | ## Developing |
| 155 | |
| 156 | ```sh |
| 157 | pnpm install |
| 158 | pnpm -r build && pnpm -r typecheck && pnpm -r test |
| 159 | pnpm --filter @diffinery/app db:migrate:local |
| 160 | pnpm dev # http://localhost:8787, signed in as dev@diffinery.local |
| 161 | pnpm --filter @diffinery/app e2e # wrangler dev + real git + the runner agent + Chromium |
| 162 | ``` |
| 163 | |
| 164 | The end-to-end test does the following: |
| 165 | |
| 166 | 1. Pushes a repository with real `git`. |
| 167 | 2. Runs its CI through the actual runner agent. |
| 168 | 3. Checks secret scanning and branch protection. |
| 169 | 4. Has an agent open a pull request that needs a human approval and a green |
| 170 | check, then merges it. |
| 171 | 5. Clones shallow, then deepens the clone. |
| 172 | 6. Drives the MCP server. |
| 173 | 7. In Chromium with a virtual authenticator, joins from an invite with a new |
| 174 | passkey, adds and removes passkeys, signs out and in, and uses an admin |
| 175 | sign-in link. |
| 176 | |
| 177 | Team preferences for product and design decisions are in |
| 178 | [docs/PREFERENCES.md](docs/PREFERENCES.md). |