Compare changes
Choose two branches to see what changed or to start a new pull request.
27 files changed+648−48
Files (27)
- CITATION.cff+6−2
- README.md+97−7
- apps/diffinery/docs/agents/review.md+2−0
- apps/diffinery/docs/agents/tasks.md+2−0
- apps/diffinery/docs/agents/today.md+2−0
- apps/diffinery/docs/collaboration/merge-on-green.md+2−0
- apps/diffinery/docs/images/agent-pull-request.webp+0−0
- apps/diffinery/docs/images/agent-task.webp+0−0
- apps/diffinery/docs/images/merge-on-green.webp+0−0
- apps/diffinery/docs/images/today.webp+0−0
- apps/diffinery/scripts/agent-screenshots.mjs+339−0
- apps/diffinery/site/agents.md+61−8
- apps/diffinery/site/features.md+12−4
- apps/diffinery/src/agentic/review.ts+2−1
- apps/diffinery/src/agentic/today-repo.ts+14−3
- apps/diffinery/src/agentic/today.ts+1−1
- apps/diffinery/src/docs/pages.ts+2−2
- apps/diffinery/src/repo/RepoDO.ts+6−1
- apps/diffinery/src/web/site.tsx+77−16
- apps/diffinery/test/unit/site.test.ts+6−0
- apps/diffinery/test/unit/today.test.ts+10−0
- context7.json+1−1
- docs/images/agent-pull-request.png+0−0
- docs/images/agent-task.png+0−0
- docs/images/merge-on-green.png+0−0
- docs/images/today.png+0−0
- package.json+6−2
modifiedCITATION.cff+6−2View file
| @@ -8,9 +8,10 @@ | ||
| 8 | 8 | Containers). It hosts Git repositories, pull requests with reviews and a merge queue, |
| 9 | 9 | issues, releases and a container registry, runs existing .github/workflows files on |
| 10 | 10 | per-job containers, and gives AI coding agents accounts of their own with |
| 11 | - server-enforced guardrails and a built-in MCP server. | |
| 11 | + server-enforced guardrails, agent tasks and agent code review that run Claude Code | |
| 12 | + or Codex in sandboxes, a built-in MCP server and plugins for Claude Code and Codex. | |
| 12 | 13 | authors: |
| 13 | - - name: "The Diffinery Authors" | |
| 14 | + - name: "Codician Team" | |
| 14 | 15 | version: "0.1.0" |
| 15 | 16 | date-released: "2026-10-25" |
| 16 | 17 | license: Apache-2.0 |
| @@ -31,4 +32,7 @@ | ||
| 31 | 32 | - serverless |
| 32 | 33 | - code-review |
| 33 | 34 | - ai-agents |
| 35 | + - ai-code-review | |
| 36 | + - claude-code | |
| 37 | + - codex | |
| 34 | 38 | - mcp |
modifiedREADME.md+97−7View file
| @@ -1,20 +1,23 @@ | ||
| 1 | 1 | # Diffinery |
| 2 | 2 | |
| 3 | -**The open-source GitHub and GitHub Actions alternative that runs entirely on Cloudflare.** Git hosting, pull requests, code review and drop-in GitHub Actions CI, built for teams of people and AI agents. Deploy it to your own Cloudflare account: no servers, no runner machines, nothing to patch. | |
| 3 | +**The open-source GitHub and GitHub Actions alternative that runs entirely on Cloudflare.** Git hosting, pull requests, code review and drop-in GitHub Actions CI, with agent tasks, agent code review and plugins for Claude Code and Codex, built for teams of people and AI agents. Deploy it to your own Cloudflare account: no servers, no runner machines, nothing to patch. | |
| 4 | 4 | |
| 5 | 5 | [](LICENSE) |
| 6 | 6 | [](https://diffinery.com/docs/actions/compatibility) |
| 7 | 7 | [](https://diffinery.com/built-on-cloudflare) |
| 8 | -[](https://diffinery.com/docs/agents/mcp) | |
| 8 | +[](https://registry.modelcontextprotocol.io/v0/servers?search=com.diffinery/diffinery) | |
| 9 | +[](plugins) | |
| 9 | 10 | [](https://diffinery.com/docs) |
| 10 | 11 | [](CONTRIBUTING.md) |
| 11 | 12 | |
| 12 | -[Website](https://diffinery.com) · [Docs](https://diffinery.com/docs) · [Deploy your own](https://diffinery.com/docs/self-hosting/deploy) · [Compare](https://diffinery.com/compare) · [Performance](https://diffinery.com/performance) · [Roadmap](ROADMAP.md) · [llms.txt](https://diffinery.com/llms.txt) | |
| 13 | +[Website](https://diffinery.com) · [Docs](https://diffinery.com/docs) · [For AI agents](https://diffinery.com/agents) · [Plugins](#plugins-and-integrations) · [Deploy your own](https://diffinery.com/docs/self-hosting/deploy) · [Compare](https://diffinery.com/compare) · [Performance](https://diffinery.com/performance) · [Roadmap](ROADMAP.md) · [llms.txt](https://diffinery.com/llms.txt) | |
| 13 | 14 | |
| 14 | 15 | [](docs/images/pull-request.png) |
| 15 | 16 | |
| 16 | 17 | Diffinery hosts Git repositories, pull requests with reviews and a merge queue, issues, releases and a container registry, and runs your existing `.github/workflows/*.yml` files as they are, marketplace actions included. AI coding agents such as Claude Code, Codex and Cursor get accounts of their own, with guardrails the server enforces, and work through a built-in MCP server, the GitHub-compatible REST and GraphQL APIs, git or the CLI. Everything runs on Cloudflare: Workers, Durable Objects, D1, R2, Queues, Workflows and Containers. It is free, open-source software under the Apache License 2.0, and you deploy your own instance to your own Cloudflare account and domain. |
| 17 | 18 | |
| 19 | +Diffinery is built for the way software gets written with agents. Give an agent a goal and get a pull request back from Claude Code or Codex in a sandbox. Have another agent review every pull request for bugs. Open **Today** each morning to see what needs you, approve, and let pull requests merge on green. Plugins for Claude Code and Codex, an MCP server in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=com.diffinery/diffinery), editor extensions and `gh` support bring all of it to the tools you already use. [What agents can do on Diffinery ↓](#built-for-ai-coding-agents) | |
| 20 | + | |
| 18 | 21 | - **Source:** [diffinery.com/codician/diffinery](https://diffinery.com/codician/diffinery), where development, CI and deploys happen, mirrored to [github.com/codician-team/diffinery](https://github.com/codician-team/diffinery). |
| 19 | 22 | - **Documentation:** [diffinery.com/docs](https://diffinery.com/docs), also as Markdown and in [llms.txt](https://diffinery.com/llms.txt) and [llms-full.txt](https://diffinery.com/llms-full.txt) for AI assistants. |
| 20 | 23 | |
| @@ -24,6 +27,8 @@ | ||
| 24 | 27 | |
| 25 | 28 | - [Who Diffinery is for](#who-diffinery-is-for) |
| 26 | 29 | - [Why Diffinery](#why-diffinery) |
| 30 | +- [Built for AI coding agents](#built-for-ai-coding-agents) | |
| 31 | +- [Plugins and integrations](#plugins-and-integrations) | |
| 27 | 32 | - [Screenshots](#screenshots) |
| 28 | 33 | - [Features](#features) |
| 29 | 34 | - [How it compares](#how-it-compares) |
| @@ -48,10 +53,88 @@ | ||
| 48 | 53 | - **Git hosting, pull requests and CI in one serverless app.** One Cloudflare Worker serves the web UI, git over HTTPS, a GitHub-compatible REST and GraphQL API, and an MCP server. Each repository is its own Durable Object with its own SQLite database, so repositories scale out instead of sharing one database server. |
| 49 | 54 | - **Your GitHub Actions workflows run unchanged.** The same triggers, expressions, matrices, `needs`, outputs, reusable workflows, environments, secrets, variables, OIDC, service containers and Docker. Marketplace actions (`uses: owner/repo@ref`) are downloaded and run. Every known difference is listed in [Compatibility with GitHub Actions](https://diffinery.com/docs/actions/compatibility). |
| 50 | 55 | - **Runners that exist only while a job runs.** Each job gets a fresh Ubuntu 24.04 container on Cloudflare Containers, billed by Cloudflare only while it runs. The default size has 4 vCPUs and 12 GiB of memory, and jobs that use little of it move to smaller sizes on their own. Self-hosted runners cover macOS, Windows, Arm and GPUs. |
| 51 | -- **Built for AI agents, with a human in the loop.** Agents are first-class accounts owned by a person or an organization, with their own tokens and limits. They can't push or merge to protected branches unless a rule allows it, their pull requests need a human approval, and every commit they make records who they act for. Agent tasks run Claude Code, Codex or your own command in a sandbox and open a pull request. | |
| 56 | +- **Built for AI agents, with a human in the loop.** Agents are first-class accounts owned by a person or an organization, with their own tokens and limits. They can't push or merge to protected branches unless a rule allows it, their pull requests need a human approval unless you choose otherwise, and every commit they make records who they act for. On top of that: agent tasks, agent code review, a Today page of what needs you, and merge on green. See [Built for AI coding agents](#built-for-ai-coding-agents). | |
| 57 | +- **Plugins for the tools agents already use.** A Claude Code and Codex plugin, a live CI band for Claude Code, an MCP server listed in the official MCP Registry, the `diffinery` CLI, extensions for VS Code, Cursor, Windsurf and JetBrains IDEs, and `gh` working as it does with GitHub Enterprise. See [Plugins and integrations](#plugins-and-integrations). | |
| 52 | 58 | - **Secure by default.** Passkey sign-in with no passwords, invite-only accounts, branch protection, push protection that blocks leaked credentials, envelope-encrypted secrets, kill switches and an append-only audit log. |
| 53 | 59 | - **Moving from GitHub is one step.** A server-side importer brings a repository or a whole organization over with its history, issues, pull requests, reviews and releases, and `gh`, Octokit, VS Code and JetBrains IDEs work against Diffinery. |
| 54 | 60 | |
| 61 | +## Built for AI coding agents | |
| 62 | + | |
| 63 | +Every agent feature is opt-in and sits on top of the Git and CI platform, so a team that uses none of them just has a forge. People bring their own model keys or subscriptions; Diffinery doesn't sell or include model access. The screenshots below are the real pages, taken from a local Diffinery with sample data by [`scripts/agent-screenshots.mjs`](apps/diffinery/scripts/agent-screenshots.mjs), which runs a real agent task, agent review and CI run. | |
| 64 | + | |
| 65 | +### Agent accounts with guardrails | |
| 66 | + | |
| 67 | +An agent is an account of its own, owned by a person or an organization, with its own tokens. Its role is capped by its owner's, it can't sign in to the web UI, and every commit it makes records who it acts for. The server enforces the same rules over Git, the REST API, MCP and sandboxes: | |
| 68 | + | |
| 69 | +| Guardrail | What it means | | |
| 70 | +| --- | --- | | |
| 71 | +| No pushes to protected branches | Agents open pull requests instead, unless a branch protection rule allows it. | | |
| 72 | +| A person approves agent pull requests | At least one approval from a person, even on a branch with no rule. An agent's approval never counts. | | |
| 73 | +| No merging protected branches | Unless a rule lets agents land through auto-merge once CI and a person agree. | | |
| 74 | +| Agents can't mark their own CI green | An agent's check results never satisfy required checks. | | |
| 75 | +| No secrets for agent-written code | A workflow run of code an agent pushed gets only its job token, so an agent can't push a workflow that sends your secrets elsewhere. | | |
| 76 | +| No changes to secrets, environments or branch protection | Whatever the agent's role. Push protection can't be bypassed either. | | |
| 77 | + | |
| 78 | +See [Agents](https://diffinery.com/docs/agents/overview) and [Guardrails](https://diffinery.com/docs/agents/overview#guardrails). | |
| 79 | + | |
| 80 | +### Agent tasks: a goal in, a pull request out | |
| 81 | + | |
| 82 | +Give one of your agents a goal. Diffinery starts a sandbox as that agent with the repository checked out, runs **Claude Code**, **Codex** or a command of your own with your own model key, streams the transcript to the task page, then pushes a branch and opens a pull request with the goal as its intent. Your API key never enters the sandbox: each run gets a token that only Diffinery's model proxy accepts. Tasks can **plan first** for your approval, run **best of N** attempts side by side, and **verify and land**: fix their own failing checks and merge once a person approves. | |
| 83 | + | |
| 84 | +To use your **Claude or ChatGPT subscription** instead of an API key, open a [terminal](https://diffinery.com/docs/agents/terminal): a shell in your browser with the repository checked out, where you sign in to Claude Code or Codex with your own plan and can hand it tasks. | |
| 85 | + | |
| 86 | +[](docs/images/agent-task.png) | |
| 87 | + | |
| 88 | +See [Agent tasks](https://diffinery.com/docs/agents/tasks) and [Terminals](https://diffinery.com/docs/agents/terminal). | |
| 89 | + | |
| 90 | +### Agent review on every pull request | |
| 91 | + | |
| 92 | +One of your agents reviews pull requests for bugs, security issues and logic errors, the way Cursor's Bugbot does. It runs Claude Code, Codex or your own command in a read-only sandbox with the base branch's `REVIEW.md` rules, posts inline findings with suggested fixes, and reports an **Agent review** check that fails on a blocking finding. **Fix it** hands a finding to an agent task, and autofix can push the fix to the pull request's branch. Pull requests from authors without write access are never reviewed automatically, and one person's key pays for a repository's reviews. | |
| 93 | + | |
| 94 | +[](docs/images/agent-pull-request.png) | |
| 95 | + | |
| 96 | +See [Agent review](https://diffinery.com/docs/agents/review). | |
| 97 | + | |
| 98 | +### Today and Needs you | |
| 99 | + | |
| 100 | +**Today** is the first page after you sign in. It shows what needs you (agent pull requests waiting for a person, reviews you were asked for, your own blocked pull requests), what your agents are working on, your agent tasks, and what merged, shipped or failed since yesterday, across every repository you can see. Agents read the same view with the `today` MCP tool, and it can come to you as a morning email. | |
| 101 | + | |
| 102 | +[](docs/images/today.png) | |
| 103 | + | |
| 104 | +See [Today](https://diffinery.com/docs/agents/today). | |
| 105 | + | |
| 106 | +### Merge on green | |
| 107 | + | |
| 108 | +Pick the checks that must pass in **Settings → Merge on green**, name the agents whose pull requests should land by themselves, and choose whether they wait for a person's approval. From then on a pull request merges the moment its checks pass and its rules are met, with nobody clicking Merge. Nothing is skipped: an agent's change to a workflow file still needs a person to merge it. | |
| 109 | + | |
| 110 | +[](docs/images/merge-on-green.png) | |
| 111 | + | |
| 112 | +See [Merge on green automatically](https://diffinery.com/docs/collaboration/merge-on-green). | |
| 113 | + | |
| 114 | +### Projects, sandboxes and docs for agents | |
| 115 | + | |
| 116 | +- **[Projects](https://diffinery.com/docs/agents/projects):** every repository has a board of its agent tasks, a conversation, project instructions and a memory every task reads. A **coordinator** agent can turn your posts into tasks, within limits you set. | |
| 117 | +- **[Sandboxes](https://diffinery.com/docs/agents/sandboxes):** a short-lived Linux container with the repository cloned, driven over MCP or the REST API, so an agent working from a chat app gets a real machine to build and test on. | |
| 118 | +- **[Docs for agents](https://diffinery.com/docs/agents/docs-for-agents):** every page as Markdown, [llms.txt](https://diffinery.com/llms.txt), [llms-full.txt](https://diffinery.com/llms-full.txt), an OpenAPI 3.1 description of the REST API, and `search_docs` and `read_docs` over MCP. | |
| 119 | + | |
| 120 | +## Plugins and integrations | |
| 121 | + | |
| 122 | +Agents trained on GitHub already know how to use Diffinery, and these bring the rest into the tools you use. Everything here works with diffinery.com and with any instance you deploy. | |
| 123 | + | |
| 124 | +| Integration | What it does | Install | | |
| 125 | +| --- | --- | --- | | |
| 126 | +| **Claude Code plugin** (`diffinery`) | The MCP server with OAuth sign-in, a skill that teaches the branch → pull request → CI → merge loop and Diffinery's agent rules, `/diffinery:pr-status` with the log tail of failing jobs, and a session hook that tells Claude it's in a Diffinery repository | `claude plugin marketplace add codician-team/diffinery`<br>`claude plugin install diffinery@diffinery` | | |
| 127 | +| **Live CI in Claude Code** (`diffinery-ci`) | A band above the prompt with the branch's pull request and its checks (`✗ 1 failed · ● 3 running · ✓ 6`), a `/diffinery-ci` pane with **Fix CI**, a status line entry, and a note to Claude when CI settles, in cloud sessions too | `claude plugin install diffinery-ci@diffinery` | | |
| 128 | +| **Codex plugin** (`diffinery`) | The same MCP server, skills and session hook for OpenAI's Codex CLI | `codex plugin marketplace add https://diffinery.com/codician/diffinery.git`<br>`codex plugin add diffinery@diffinery` | | |
| 129 | +| **MCP server** | 52 tools on diffinery.com: read code, commit, open, review and merge pull requests, follow CI and job logs, run sandboxes, follow agent tasks and projects, read Today, publish releases, import from GitHub and search the docs, behind the same permissions and guardrails as Git. Listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=com.diffinery/diffinery) as `com.diffinery/diffinery` | `https://<your host>/mcp`, with a browser sign-in (OAuth 2.1) or an agent token | | |
| 130 | +| **Claude (claude.ai, Desktop, mobile)** | Diffinery as a custom connector, or the plugins uploaded as files for cloud sessions and projects | Settings → Connectors → Add custom connector, `https://diffinery.com/mcp` | | |
| 131 | +| **`diffinery` CLI** | Repositories, pull requests with `--intent`, auto-merge, runs and logs, secrets, runners and migrations; `diffinery setup` signs you in and sets up git, `gh` and both Claude Code plugins in one go | `curl -fsSL https://diffinery.com/install \| bash` | | |
| 132 | +| **GitHub CLI (`gh`)** | `gh pr create`, `gh pr checks`, `gh pr merge` and the rest, through Diffinery's GitHub-compatible REST and GraphQL APIs; Claude Code shows the branch's pull request and CI status from it | `diffinery auth setup-gh` | | |
| 133 | +| **VS Code, Cursor, Windsurf** | **Clone from Diffinery** with a passkey sign-in; Git in the editor pushes and pulls with that sign-in | [diffinery.com/install-editor](https://diffinery.com/install-editor) | | |
| 134 | +| **JetBrains IDEs** | **Get from Version Control** with a passkey sign-in, in IntelliJ IDEA, PyCharm, WebStorm and the rest (2024.3 or later) | Plugin repository `https://diffinery.com/-/jetbrains/updatePlugins.xml` | | |
| 135 | + | |
| 136 | +The plugins' sources are in [`plugins/`](plugins), the extensions' in [`apps/vscode`](apps/vscode) and [`apps/jetbrains`](apps/jetbrains), and the CLI's in [`apps/cli`](apps/cli). Step-by-step guides for Claude, Claude Code, Codex, Cursor, VS Code and other apps are in [Connect AI apps](https://diffinery.com/docs/connect). | |
| 137 | + | |
| 55 | 138 | ## Screenshots |
| 56 | 139 | |
| 57 | 140 | | A repository, with the clone URL and one-click open in VS Code, Cursor, Windsurf and IntelliJ IDEA | A workflow run from `.github/workflows/ci.yml`, with a matrix and `needs` | |
| @@ -69,8 +152,10 @@ | ||
| 69 | 152 | | Issues and releases | Issues, labels, milestones, reactions, mentions, releases with assets and generated notes | [Collaboration](https://diffinery.com/docs/collaboration/issues) | |
| 70 | 153 | | Actions (CI/CD) | GitHub Actions–compatible workflows on hosted runners in Cloudflare Containers or self-hosted runners; caches, artifacts, secrets, environments, OIDC, Docker, service containers, concurrency groups, scheduled runs | [Actions overview](https://diffinery.com/docs/actions/overview) | |
| 71 | 154 | | Container registry | Push and pull OCI images at `<your host>/<owner>/<name>`, in place of ghcr.io | [Container registry](https://diffinery.com/docs/packages/container-registry) | |
| 72 | -| Agents | Agent accounts and tokens, an MCP server at `/mcp`, sandboxes, agent tasks, agent code review, projects with shared memory, and a Today page of what needs you | [Agents](https://diffinery.com/docs/agents/overview) | | |
| 73 | -| API and tools | GitHub-compatible REST (`/api/v3`) and GraphQL (`/api/graphql`) APIs, webhooks, the `diffinery` CLI, `gh` support, VS Code and JetBrains plugins, a Claude Code and Codex plugin (in [`plugins/`](plugins)) | [REST API](https://diffinery.com/docs/reference/rest-api) | | |
| 155 | +| Agents | Agent accounts with server-enforced guardrails, agent tasks with Claude Code or Codex (plan first, best of N, verify and land), agent code review with inline findings and an Agent review check, browser terminals on your own Claude or ChatGPT plan, projects with a coordinator and shared memory, sandboxes, and a Today page of what needs you | [Agents](https://diffinery.com/docs/agents/overview) | | |
| 156 | +| Merge on green | Pull requests from people and agents merge by themselves once their required checks pass and their branch's rules are met, set up in a few clicks | [Merge on green](https://diffinery.com/docs/collaboration/merge-on-green) | | |
| 157 | +| Plugins and integrations | A Claude Code and Codex plugin, a live CI band for Claude Code, an MCP server in the official MCP Registry, the `diffinery` CLI, `gh` support, and VS Code, Cursor, Windsurf and JetBrains extensions | [Plugins and integrations](#plugins-and-integrations) | | |
| 158 | +| API | GitHub-compatible REST (`/api/v3`) and GraphQL (`/api/graphql`) APIs, described as OpenAPI 3.1, and GitHub-compatible webhooks | [REST API](https://diffinery.com/docs/reference/rest-api) | | |
| 74 | 159 | | Security | Passkeys (WebAuthn), branch protection, push protection, encrypted secrets, audit log, moderation, rate limits, quotas and kill switches | [Security overview](https://diffinery.com/docs/security/overview) | |
| 75 | 160 | | Migration | Import from GitHub with history, issues, pull requests, reviews and releases, from the web, CLI, API or MCP | [Migrate from GitHub](https://diffinery.com/docs/migrate-from-github) | |
| 76 | 161 | |
| @@ -213,8 +298,11 @@ | ||
| 213 | 298 | No. Containers, Durable Objects with SQLite, Workflows and Queues need the Workers Paid plan. See [Deploy your own Diffinery](https://diffinery.com/docs/self-hosting/deploy). |
| 214 | 299 | |
| 215 | 300 | **Does it work with AI coding agents?** |
| 216 | -Yes. Agents connect over MCP (`https://<your host>/mcp`), the REST API, git or the GitHub CLI. There are guides for Claude, Claude Code, Codex, Cursor and VS Code, and a plugin for Claude Code and Codex, installable with `claude plugin marketplace add https://diffinery.com/codician/diffinery.git` (sources in [`plugins/`](plugins)). See [Connect AI apps](https://diffinery.com/docs/connect). | |
| 301 | +Yes. Agents connect over MCP (`https://<your host>/mcp`, listed in the official MCP Registry as `com.diffinery/diffinery`), the REST API, git or the GitHub CLI. There are guides for Claude, Claude Code, Codex, Cursor and VS Code, and plugins for Claude Code and Codex, installable with `claude plugin marketplace add codician-team/diffinery` (or `https://diffinery.com/codician/diffinery.git`; sources in [`plugins/`](plugins)). Diffinery can also run them for you: agent tasks run Claude Code or Codex in a sandbox and open a pull request, and agent review checks every pull request. See [Built for AI coding agents](#built-for-ai-coding-agents) and [Connect AI apps](https://diffinery.com/docs/connect). | |
| 217 | 302 | |
| 303 | +**Can I use my Claude or ChatGPT subscription instead of an API key?** | |
| 304 | +In a terminal, yes: a terminal is a shell in your browser with the repository checked out, where you sign in to Claude Code or Codex with your own plan and can give it tasks that open pull requests. Headless agent tasks and agent review use an API key you add (Anthropic, OpenAI or OpenRouter), because the providers allow automated use with API keys. Codex on a ChatGPT plan is offered on private repositories only, as OpenAI allows. See [Terminals](https://diffinery.com/docs/agents/terminal). | |
| 305 | + | |
| 218 | 306 | **Can an AI agent merge its own pull request?** |
| 219 | 307 | Not by default. Unless a branch protection rule allows it, agents can't push to or merge into protected branches, their pull requests need a person's approval, their check results never satisfy required checks, and runs of code they pushed get no secrets. See [Guardrails](https://diffinery.com/docs/agents/overview#guardrails). |
| 220 | 308 | |
| @@ -237,3 +325,5 @@ | ||
| 237 | 325 | - **Security:** please don't open public issues for vulnerabilities. Report them privately as described in [SECURITY.md](SECURITY.md). |
| 238 | 326 | - **Citing Diffinery:** use GitHub's "Cite this repository" button, or [CITATION.cff](CITATION.cff). |
| 239 | 327 | - **License:** [Apache License 2.0](LICENSE). See [NOTICE](NOTICE). |
| 328 | + | |
| 329 | +Diffinery is made by the Codician team. |
modifiedapps/diffinery/docs/agents/review.md+2−0View file
| @@ -5,6 +5,8 @@ | ||
| 5 | 5 | |
| 6 | 6 | **Agent review** has one of your agents review pull requests, the way Cursor's Bugbot does. The agent runs Claude Code, Codex or a command of your own in a read-only sandbox, using a model key you bring. It reads the pull request's changes and looks for bugs, security issues and logic errors, not style. Diffinery then posts what it found as a review with inline comments, and reports an **Agent review** check that fails when a finding is blocking. |
| 7 | 7 | |
| 8 | + | |
| 9 | + | |
| 8 | 10 | Agent review is off until a repository admin turns it on. Nothing changes for a repository that doesn't use it. |
| 9 | 11 | |
| 10 | 12 | ## Turn it on |
modifiedapps/diffinery/docs/agents/tasks.md+2−0View file
| @@ -5,6 +5,8 @@ | ||
| 5 | 5 | |
| 6 | 6 | An **agent task** is a goal you give one of your agents, such as "Fix the Contact sales link on the pricing page and add a test". Diffinery starts a sandbox as that agent with the repository checked out, runs a coding agent (Claude Code, Codex or a command of your own) with **your own model API key**, streams its transcript to the task page, then pushes the work to a branch and opens a pull request as the agent. The pull request needs a person's approval like any agent pull request. |
| 7 | 7 | |
| 8 | + | |
| 9 | + | |
| 8 | 10 | Tasks are optional. Nothing changes for a repository until someone with a model key starts one. To use your Claude or ChatGPT subscription instead of an API key, run tasks in a [terminal](/docs/agents/terminal#give-it-a-task). |
| 9 | 11 | |
| 10 | 12 | The same machinery reviews pull requests: see [Agent review](/docs/agents/review). Reviews show in the repository's task list, labelled **Review**. The repository's **Project** tab shows tasks on a board, with a conversation and a memory every task reads: see [Projects](/docs/agents/projects). |
modifiedapps/diffinery/docs/agents/today.md+2−0View file
| @@ -7,6 +7,8 @@ | ||
| 7 | 7 | |
| 8 | 8 | The same view is available to scripts and agents from the REST API and the `today` MCP tool, so your own coding agent can read your morning too. It can also come to you by email as a [morning digest](#the-morning-digest), and it fits a phone's screen. |
| 9 | 9 | |
| 10 | + | |
| 11 | + | |
| 10 | 12 | ## What it shows |
| 11 | 13 | |
| 12 | 14 | At the top, four numbers link to the sections below them: how many items need you, how many agent pull requests are open, how many pull requests merged since yesterday (and how many of them were by agents), and how many workflow runs failed. |
modifiedapps/diffinery/docs/collaboration/merge-on-green.md+2−0View file
| @@ -10,6 +10,8 @@ | ||
| 10 | 10 | |
| 11 | 11 | After that, every new pull request merges on green. Nothing is skipped: auto-merge waits for every rule of the branch, a person's pull request still needs the approvals the branch asks for, and a change an agent makes to a workflow file still needs a person to merge it. |
| 12 | 12 | |
| 13 | + | |
| 14 | + | |
| 13 | 15 | ## The quickest path (a repository you work on alone) |
| 14 | 16 | |
| 15 | 17 | 1. Push a workflow that runs on pull requests (for example `on: pull_request`; see [Actions](/docs/actions/overview)), open one pull request, and let its checks run once. |
addedapps/diffinery/docs/images/agent-pull-request.webp+0−0View file
Binary file not shown.
addedapps/diffinery/docs/images/agent-task.webp+0−0View file
Binary file not shown.
addedapps/diffinery/docs/images/merge-on-green.webp+0−0View file
Binary file not shown.
addedapps/diffinery/docs/images/today.webp+0−0View file
Binary file not shown.
addedapps/diffinery/scripts/agent-screenshots.mjs+339−0View file
| @@ -1,0 +1,339 @@ | ||
| 1 | +// Takes the screenshots of Diffinery's agent features (Today, an agent task, an agent review, a pull request that | |
| 2 | +// merges on green) from a real local Worker with sample data, for the README and the docs: | |
| 3 | +// | |
| 4 | +// pnpm --filter @diffinery/app build && pnpm --filter @diffinery/runner build | |
| 5 | +// node scripts/agent-screenshots.mjs | |
| 6 | +// | |
| 7 | +// It runs the real thing end to end: a sandbox pool (`agent.mjs --sandbox-pool`), an agent task with the Claude Code | |
| 8 | +// runtime, the automatic agent review of the pull request it opens, and merge on green. The one stand-in is Claude Code | |
| 9 | +// itself: a script with its name prints what Claude Code prints (`stream-json`) and changes the files, so no model or | |
| 10 | +// subscription is needed. Then the Worker restarts as https://diffinery.com, so the pages show the addresses people see. | |
| 11 | +// | |
| 12 | +// Writes docs/images/{today,agent-task,agent-pull-request,merge-on-green}.webp here and the same pictures as PNG in the | |
| 13 | +// repository's docs/images/ (the README's). | |
| 14 | +import { spawn, execFileSync } from "node:child_process"; | |
| 15 | +import { createHash, randomBytes } from "node:crypto"; | |
| 16 | +import { appendFileSync, chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; | |
| 17 | +import { tmpdir } from "node:os"; | |
| 18 | +import { join, dirname, resolve } from "node:path"; | |
| 19 | +import { fileURLToPath } from "node:url"; | |
| 20 | +import { chromium } from "playwright"; | |
| 21 | +import { migratedState } from "../test/e2e/d1-state.mjs"; | |
| 22 | +import { freePort } from "../test/e2e/ports.mjs"; | |
| 23 | + | |
| 24 | +const APP = resolve(dirname(fileURLToPath(import.meta.url)), ".."); | |
| 25 | +const AGENT = resolve(APP, "../../containers/runner/dist/agent.mjs"); | |
| 26 | +const OUT = join(APP, "docs/images"); | |
| 27 | +const README_OUT = resolve(APP, "../../docs/images"); | |
| 28 | +const top = mkdtempSync(join(tmpdir(), "diffinery-agent-shots-")); | |
| 29 | +const work = join(top, "work"); | |
| 30 | +const poolRoot = join(top, "pool"); | |
| 31 | +const poolHome = join(top, "home"); | |
| 32 | +const persist = join(work, "state"); | |
| 33 | +const bin = join(work, "bin"); | |
| 34 | +for (const d of [work, poolHome, bin]) mkdirSync(d, { recursive: true }); | |
| 35 | +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
| 36 | +const hash = (s) => createHash("sha256").update(s).digest("hex"); | |
| 37 | +const SESSION = `agent-shots-${randomBytes(12).toString("hex")}`; | |
| 38 | +const procs = []; | |
| 39 | +let log = ""; | |
| 40 | + | |
| 41 | +function start(cmd, args, env) { | |
| 42 | + const p = spawn(cmd, args, { cwd: APP, env: { ...process.env, CI: "1", WRANGLER_SEND_METRICS: "false", ...env }, stdio: ["ignore", "pipe", "pipe"], detached: true }); | |
| 43 | + // LOG=<file> keeps everything the Worker and the pool print, for when a step times out. | |
| 44 | + const keep = (d) => { | |
| 45 | + log += d; | |
| 46 | + if (process.env.LOG) appendFileSync(process.env.LOG, d); | |
| 47 | + }; | |
| 48 | + p.stdout.on("data", keep); | |
| 49 | + p.stderr.on("data", keep); | |
| 50 | + procs.push(p); | |
| 51 | + return p; | |
| 52 | +} | |
| 53 | +const stop = (p) => { | |
| 54 | + try { | |
| 55 | + process.kill(-p.pid, "SIGTERM"); | |
| 56 | + } catch {} | |
| 57 | +}; | |
| 58 | +async function until(what, fn, ms = 120_000) { | |
| 59 | + let v; | |
| 60 | + for (const end = Date.now() + ms; Date.now() < end; await sleep(500)) { | |
| 61 | + v = await fn(); | |
| 62 | + if (v) return v; | |
| 63 | + } | |
| 64 | + throw new Error(`timed out waiting for ${what}\n${log.slice(-4000)}`); | |
| 65 | +} | |
| 66 | +const sql = (command) => execFileSync("pnpm", ["exec", "wrangler", "d1", "execute", "diffinery-dev", "--local", "-e", "dev", "--persist-to", persist, "--command", command], { cwd: APP, stdio: "pipe", env: { ...process.env, CI: "1" } }); | |
| 67 | + | |
| 68 | +// ---------------------------------------------------------------- the stand-in for Claude Code | |
| 69 | + | |
| 70 | +// `npm install --global @anthropic-ai/claude-code@…` (the runtime's install step) and `claude`, as the jail runs them. | |
| 71 | +writeFileSync(join(bin, "npm"), "#!/bin/sh\nexit 0\n"); | |
| 72 | +writeFileSync( | |
| 73 | + join(bin, "claude"), | |
| 74 | + `#!/usr/bin/env node | |
| 75 | +const fs = require("fs"); | |
| 76 | +if (process.argv.includes("--version")) return console.log("2.1.296 (Claude Code)"); | |
| 77 | +const prompt = process.env.DIFFINERY_TASK_PROMPT || ""; | |
| 78 | +const say = (o) => console.log(JSON.stringify(o)); | |
| 79 | +const text = (t) => say({ type: "assistant", message: { content: [{ type: "text", text: t }] } }); | |
| 80 | +const tool = (name, input, out) => { | |
| 81 | + say({ type: "assistant", message: { content: [{ type: "tool_use", name, input }] } }); | |
| 82 | + if (out) say({ type: "user", message: { content: [{ type: "tool_result", content: out }] } }); | |
| 83 | +}; | |
| 84 | +say({ type: "system", subtype: "init", model: "claude-sonnet" }); | |
| 85 | +if (prompt.startsWith("You are reviewing")) { | |
| 86 | + text("I'll read the changes and the tests around them."); | |
| 87 | + tool("Read", { file_path: "src/discounts.js" }); | |
| 88 | + tool("Read", { file_path: "src/checkout.js" }); | |
| 89 | + tool("Bash", { command: "npm test" }, "✔ 9 tests passed"); | |
| 90 | + const findings = { | |
| 91 | + summary: "Adds discount codes at checkout. One case worth covering: codes are matched case-sensitively.", | |
| 92 | + findings: [{ path: "src/discounts.js", line: 8, side: "RIGHT", severity: "not_blocking", title: "Codes typed in lower case are rejected", body: "Customers often type codes as they read them in an email, such as \`welcome10\`. \`CODES[code]\` only finds \`WELCOME10\`, so the code is refused with no hint why.", suggestion: " const rule = CODES[code.trim().toUpperCase()];" }], | |
| 93 | + }; | |
| 94 | + say({ type: "result", subtype: "success", num_turns: 4, result: "Reviewed the changes.\\n\`\`\`json\\n" + JSON.stringify(findings) + "\\n\`\`\`" }); | |
| 95 | + return; | |
| 96 | +} | |
| 97 | +text("I'll add discount codes to checkout: a table of codes, applying one to the cart total, and tests."); | |
| 98 | +tool("Read", { file_path: "src/checkout.js" }); | |
| 99 | +tool("Bash", { command: "npm test" }, "✔ 6 tests passed"); | |
| 100 | +fs.writeFileSync("src/discounts.js", \`// Discount codes customers can enter at checkout. | |
| 101 | +const CODES = { | |
| 102 | + WELCOME10: { percent: 10 }, | |
| 103 | + FREESHIP: { freeShipping: true }, | |
| 104 | +}; | |
| 105 | + | |
| 106 | +export function applyDiscount(cart, code) { | |
| 107 | + const rule = CODES[code]; | |
| 108 | + if (!rule) throw new Error(\\\`Unknown discount code: \\\${code}\\\`); | |
| 109 | + const subtotal = rule.percent ? Math.round(cart.subtotal * (100 - rule.percent)) / 100 : cart.subtotal; | |
| 110 | + return { ...cart, subtotal, shipping: rule.freeShipping ? 0 : cart.shipping, code }; | |
| 111 | +} | |
| 112 | +\`); | |
| 113 | +tool("Write", { file_path: "src/discounts.js" }); | |
| 114 | +const checkout = fs.readFileSync("src/checkout.js", "utf8"); | |
| 115 | +if (!checkout.includes("totalWithCode")) fs.writeFileSync("src/checkout.js", checkout.replace("export function total(cart) {", 'import { applyDiscount } from "./discounts.js";\\n\\nexport function totalWithCode(cart, code) {\\n return total(code ? applyDiscount(cart, code) : cart);\\n}\\n\\nexport function total(cart) {')); | |
| 116 | +tool("Edit", { file_path: "src/checkout.js" }); | |
| 117 | +fs.writeFileSync("test/discounts.test.js", \`import { test } from "node:test"; | |
| 118 | +import assert from "node:assert/strict"; | |
| 119 | +import { applyDiscount } from "../src/discounts.js"; | |
| 120 | + | |
| 121 | +test("WELCOME10 takes 10% off the subtotal", () => { | |
| 122 | + assert.equal(applyDiscount({ subtotal: 50, shipping: 5 }, "WELCOME10").subtotal, 45); | |
| 123 | +}); | |
| 124 | + | |
| 125 | +test("FREESHIP removes shipping", () => { | |
| 126 | + assert.equal(applyDiscount({ subtotal: 50, shipping: 5 }, "FREESHIP").shipping, 0); | |
| 127 | +}); | |
| 128 | + | |
| 129 | +test("an unknown code is refused", () => { | |
| 130 | + assert.throws(() => applyDiscount({ subtotal: 50, shipping: 5 }, "NOPE"), /Unknown discount code/); | |
| 131 | +}); | |
| 132 | +\`); | |
| 133 | +tool("Write", { file_path: "test/discounts.test.js" }); | |
| 134 | +tool("Bash", { command: "npm test" }, "✔ 9 tests passed"); | |
| 135 | +say({ type: "result", subtype: "success", num_turns: 9, result: "Added discount codes at checkout: src/discounts.js with WELCOME10 and FREESHIP, totalWithCode() in src/checkout.js, and three tests. All 9 tests pass." }); | |
| 136 | +`, | |
| 137 | +); | |
| 138 | +chmodSync(join(bin, "npm"), 0o755); | |
| 139 | +chmodSync(join(bin, "claude"), 0o755); | |
| 140 | + | |
| 141 | +// ---------------------------------------------------------------- a Worker with a sandbox pool | |
| 142 | + | |
| 143 | +const poolPort = await freePort(); | |
| 144 | +const poolSecret = randomBytes(16).toString("hex"); | |
| 145 | +const pool = start("node", [AGENT, "--sandbox-pool", "--work-root", poolRoot], { HOME: poolHome, PATH: `${bin}:${process.env.PATH}`, DIFFINERY_SANDBOX_POOL_SECRET: poolSecret, DIFFINERY_SANDBOX_PORT: String(poolPort), DIFFINERY_JAIL_SHARED_FOR_TESTS: work }); | |
| 146 | +await until("the sandbox pool", async () => log.includes("listening"), 30_000); | |
| 147 | + | |
| 148 | +migratedState(APP, persist); | |
| 149 | +const at = (hoursAgo) => new Date(Date.now() - hoursAgo * 3600_000).toISOString(); | |
| 150 | +sql(`INSERT INTO accounts (id, login, kind, name, email, status, site_admin, created_at) VALUES ('ada1', 'ada', 'user', 'Ada Lovelace', 'ada@example.com', 'active', 1, '${at(24 * 30)}')`); | |
| 151 | + | |
| 152 | +const MASTER_KEY = randomBytes(32).toString("base64"); | |
| 153 | +async function worker(varsFor) { | |
| 154 | + const port = await freePort(); | |
| 155 | + const base = `http://127.0.0.1:${port}`; | |
| 156 | + const vars = varsFor(base); | |
| 157 | + const p = start("pnpm", ["exec", "wrangler", "dev", "-e", "dev", "--ip", "127.0.0.1", "--port", String(port), "--inspector-port", String(await freePort()), "--persist-to", persist, ...Object.entries({ MASTER_KEY, ...vars }).flatMap(([k, v]) => ["--var", `${k}:${v}`]), "--show-interactive-dev-session=false"]); | |
| 158 | + await until("wrangler dev", async () => { | |
| 159 | + try { | |
| 160 | + return (await fetch(`${base}/healthz`)).ok; | |
| 161 | + } catch { | |
| 162 | + return false; | |
| 163 | + } | |
| 164 | + }); | |
| 165 | + return { p, base }; | |
| 166 | +} | |
| 167 | + | |
| 168 | +// Phase 1: the Worker at its own address, which the sandboxes clone from, signed in as Ada (DEV_AUTH). | |
| 169 | +let { p: dev, base } = await worker((b) => ({ PUBLIC_URL: b, DEV_AUTH: "ada@example.com", SANDBOX_POOL_URL: `http://127.0.0.1:${poolPort}`, SANDBOX_POOL_SECRET: poolSecret, AGENT_POLL_MS: "500" })); | |
| 170 | +let token; | |
| 171 | +async function api(method, path, body, tok = token) { | |
| 172 | + const res = await fetch(`${base}/api/v3${path}`, { method, headers: { ...(tok ? { authorization: `Bearer ${tok}` } : {}), "content-type": "application/json" }, body: body === undefined ? undefined : JSON.stringify(body) }); | |
| 173 | + const text = await res.text(); | |
| 174 | + if (!res.ok) throw new Error(`${method} ${path} -> ${res.status} ${text}`); | |
| 175 | + return text ? JSON.parse(text) : null; | |
| 176 | +} | |
| 177 | +const asAda = (method, path, body) => api(method, path, body, null); | |
| 178 | +function git(cwd, args, tok = token) { | |
| 179 | + const auth = Buffer.from(`x-token:${tok}`).toString("base64"); | |
| 180 | + return execFileSync("git", ["-c", `http.extraHeader=Authorization: Basic ${auth}`, ...args], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_AUTHOR_NAME: "Ada Lovelace", GIT_AUTHOR_EMAIL: "ada@example.com", GIT_COMMITTER_NAME: "Ada Lovelace", GIT_COMMITTER_EMAIL: "ada@example.com" } }); | |
| 181 | +} | |
| 182 | + | |
| 183 | +const browser = await chromium.launch(process.env.CHROMIUM_PATH ? { executablePath: process.env.CHROMIUM_PATH } : {}); | |
| 184 | +try { | |
| 185 | + token = (await asAda("POST", "/diffinery/tokens", { name: "screenshots", scopes: ["admin"] })).token; | |
| 186 | + await asAda("POST", "/user/orgs", { login: "acme", name: "Acme" }); | |
| 187 | + await api("POST", "/orgs/acme/repos", { name: "storefront", description: "The Acme web shop", private: true }); | |
| 188 | + const R = "/repos/acme/storefront"; | |
| 189 | + | |
| 190 | + // A small shop, with CI. | |
| 191 | + const src = join(work, "storefront"); | |
| 192 | + const files = { | |
| 193 | + "README.md": "# storefront\n\nThe Acme web shop: cart, checkout and payments.\n", | |
| 194 | + "package.json": JSON.stringify({ name: "storefront", private: true, type: "module", scripts: { test: "node --test" } }, null, 2) + "\n", | |
| 195 | + "src/checkout.js": "export function total(cart) {\n return Math.round((cart.subtotal + cart.shipping) * 100) / 100;\n}\n", | |
| 196 | + "src/cart.js": "export function addItem(cart, item) {\n const subtotal = cart.subtotal + item.price * item.quantity;\n return { ...cart, items: [...cart.items, item], subtotal };\n}\n", | |
| 197 | + "test/checkout.test.js": 'import { test } from "node:test";\nimport assert from "node:assert/strict";\nimport { total } from "../src/checkout.js";\n\ntest("adds shipping", () => assert.equal(total({ subtotal: 10, shipping: 5 }), 15));\n', | |
| 198 | + "REVIEW.md": "Prices are in dollars with two decimals. Customer-facing errors must say what to do next.\n", | |
| 199 | + // A self-hosted runner here (this script runs its jobs with the real runner agent); on diffinery.com it would be ubuntu-latest. | |
| 200 | + ".github/workflows/ci.yml": "name: CI\non:\n push:\n branches: [main]\n pull_request:\njobs:\n test:\n runs-on: self-hosted\n steps:\n - uses: actions/checkout@v4\n - run: node --test\n", | |
| 201 | + }; | |
| 202 | + for (const [f, c] of Object.entries(files)) { | |
| 203 | + mkdirSync(dirname(join(src, f)), { recursive: true }); | |
| 204 | + writeFileSync(join(src, f), c); | |
| 205 | + } | |
| 206 | + git(src, ["init", "-q", "-b", "main"]); | |
| 207 | + git(src, ["add", "-A"]); | |
| 208 | + git(src, ["commit", "-q", "-m", "Cart and checkout"]); | |
| 209 | + git(src, ["push", "-q", `${base}/acme/storefront.git`, "main"]); | |
| 210 | + | |
| 211 | + // Ada's model key (a stand-in: the stand-in Claude Code never calls a model), and two agents of Acme's. | |
| 212 | + await asAda("PUT", "/diffinery/model-keys/anthropic", { key: `sk-ant-api03-${randomBytes(30).toString("hex")}`, accept_terms: true }); | |
| 213 | + const coder = (await asAda("POST", "/diffinery/agents", { login: "acme-coder", name: "Acme coder", org: "acme", auto_merge: true })).token.token; | |
| 214 | + await asAda("POST", "/diffinery/agents", { login: "acme-reviewer", name: "Acme reviewer", org: "acme" }); | |
| 215 | + | |
| 216 | + // CI: a self-hosted runner that runs every job the repository queues with the real runner agent, as `diffinery runner` does. | |
| 217 | + const reg = await api("POST", `${R}/actions/runners/registration-token`); | |
| 218 | + const runner = (await api("POST", `${R}/actions/runners/register`, { registration_token: reg.token, name: "ci-1", labels: ["linux"] })).token; | |
| 219 | + let running = true; | |
| 220 | + const jobs = (async () => { | |
| 221 | + while (running) { | |
| 222 | + const r = await fetch(`${base}/api/v3${R}/actions/runners/claim`, { method: "POST", headers: { authorization: `Bearer ${runner}`, "content-type": "application/json" }, body: "{}" }).catch(() => null); | |
| 223 | + if (r?.status !== 200) { | |
| 224 | + await sleep(1000); | |
| 225 | + continue; | |
| 226 | + } | |
| 227 | + const claim = await r.json(); | |
| 228 | + await new Promise((done) => { | |
| 229 | + const job = spawn(process.execPath, [AGENT], { env: { ...process.env, DIFFINERY_API_URL: claim.api_url, DIFFINERY_JOB_ID: claim.job_id, DIFFINERY_JOB_TOKEN: claim.token, DIFFINERY_WORK_ROOT: join(work, `job-${claim.job_id}`) }, stdio: "ignore" }); | |
| 230 | + job.on("close", done); | |
| 231 | + }); | |
| 232 | + } | |
| 233 | + })(); | |
| 234 | + const green = (sha) => | |
| 235 | + until(`CI on ${sha.slice(0, 7)}`, async () => { | |
| 236 | + const runs = ((await api("GET", `${R}/commits/${sha}/check-runs`)).check_runs ?? []).filter((c) => c.name.startsWith("CI")); | |
| 237 | + if (runs.some((c) => c.conclusion && c.conclusion !== "success")) throw new Error(`CI failed on ${sha}: ${JSON.stringify(runs)}`); | |
| 238 | + return runs.length && runs.every((c) => c.conclusion === "success"); | |
| 239 | + }, 300_000); | |
| 240 | + | |
| 241 | + // An agent pull request from earlier today, approved by Ada and merged. | |
| 242 | + git(src, ["checkout", "-q", "-b", "fix-rounding"]); | |
| 243 | + writeFileSync(join(src, "src/cart.js"), files["src/cart.js"].replace("cart.subtotal + item.price * item.quantity", "Math.round((cart.subtotal + item.price * item.quantity) * 100) / 100")); | |
| 244 | + git(src, ["commit", "-qam", "Round the cart subtotal to cents"]); | |
| 245 | + git(src, ["push", "-q", `${base}/acme/storefront.git`, "fix-rounding"], coder); | |
| 246 | + const early = await api("POST", `${R}/pulls`, { title: "Round the cart subtotal to cents", head: "fix-rounding", base: "main", body: "Adding 0.1 + 0.2 showed $0.30000000000000004 in the cart.", intent: "Cart totals always show whole cents." }, coder); | |
| 247 | + await green(early.head.sha); | |
| 248 | + await api("POST", `${R}/pulls/${early.number}/reviews`, { event: "APPROVE", body: "Thanks!" }); | |
| 249 | + await until("the first pull request to merge", async () => (await api("GET", `${R}/pulls/${early.number}`)).merged || (await api("PUT", `${R}/pulls/${early.number}/merge`, { merge_method: "squash" }).catch(() => null))); | |
| 250 | + git(src, ["checkout", "-q", "main"]); | |
| 251 | + | |
| 252 | + // Merge on green: the CI checks must pass, and acme-coder's pull requests land once a person approves. | |
| 253 | + const page = await browser.newPage(); | |
| 254 | + await asAda("PUT", `${R}/diffinery/agent-review`, { agent: "acme-reviewer", runtime: "claude-code", trigger: "push", autofix: false, accept_terms: true }); | |
| 255 | + await page.goto(`${base}/acme/storefront/settings/merge-on-green`); | |
| 256 | + await page.waitForLoadState("networkidle"); | |
| 257 | + await page.locator('textarea[name="otherChecks"]').fill("CI / test"); | |
| 258 | + const others = page.locator('input[name="otherAgents"]'); | |
| 259 | + if (await others.count()) await others.fill("acme-coder"); | |
| 260 | + // Agents' pull requests still wait for a person's approval (the page's default lands them as soon as the checks pass). | |
| 261 | + await page.getByText("Once the checks pass and a person approved").click(); | |
| 262 | + await page.waitForFunction(() => document.querySelector('input[name="approval"]')?.value === "approval"); | |
| 263 | + await page.getByRole("button", { name: /Turn on merge on green|Save/ }).click(); | |
| 264 | + await page.waitForLoadState("networkidle"); | |
| 265 | + | |
| 266 | + // The task: Ada asks acme-coder for a feature, Claude Code writes it in a sandbox and opens a pull request. | |
| 267 | + const task = await asAda("POST", `${R}/diffinery/tasks`, { goal: "Add discount codes at checkout\n\nLet customers enter a code in the cart. Support WELCOME10 (10% off) and FREESHIP, with tests.", runtime: "claude-code", agent: "acme-coder" }); | |
| 268 | + const done = await until("the task", async () => { | |
| 269 | + const t = await api("GET", `${R}/diffinery/tasks/${task.number}`); | |
| 270 | + if (t.state === "failed") throw new Error(`the task failed: ${JSON.stringify(await api("GET", `${R}/diffinery/tasks/${task.number}/transcript`))}`); | |
| 271 | + return t.state !== "running" && t.pull_request ? t : null; | |
| 272 | + }, 300_000); | |
| 273 | + const pr = await api("GET", `${R}/pulls/${done.pull_request.number}`); | |
| 274 | + await green(pr.head.sha); | |
| 275 | + await until("the agent review", async () => { | |
| 276 | + const r = await api("GET", `${R}/pulls/${pr.number}/diffinery/agent-review`); | |
| 277 | + if (process.env.DEBUG && r.task) console.error("review", JSON.stringify((await api("GET", `${R}/diffinery/tasks/${r.task.number}`)).runs)); | |
| 278 | + return r.findings?.length && r.reviewed_sha === pr.head.sha ? r : null; | |
| 279 | + }, 300_000); | |
| 280 | + | |
| 281 | + // Another agent pull request, still being worked on. | |
| 282 | + git(src, ["checkout", "-q", "-b", "agent/order-emails"]); | |
| 283 | + writeFileSync(join(src, "src/emails.js"), "export const orderConfirmation = (order) => `Thanks for your order #${order.id}!`;\n"); | |
| 284 | + git(src, ["add", "-A"]); | |
| 285 | + git(src, ["commit", "-qm", "Order confirmation email"]); | |
| 286 | + git(src, ["push", "-q", `${base}/acme/storefront.git`, "agent/order-emails"], coder); | |
| 287 | + const draft = await api("POST", `${R}/pulls`, { title: "Send an order confirmation email", head: "agent/order-emails", base: "main", draft: true, intent: "Customers get an email with their order number right after paying." }, coder); | |
| 288 | + await green(draft.head.sha); | |
| 289 | + running = false; | |
| 290 | + await jobs; | |
| 291 | + | |
| 292 | + // Phase 2: the same data, served as https://diffinery.com to Ada's signed-in browser. | |
| 293 | + stop(dev); | |
| 294 | + await sleep(2000); | |
| 295 | + sql(`INSERT INTO sessions (id, account_id, method, created_at, last_seen_at, expires_at) VALUES ('${hash(SESSION)}', 'ada1', 'passkey', '${at(0)}', '${at(0)}', '${new Date(Date.now() + 86400_000).toISOString()}')`); | |
| 296 | + ({ p: dev, base } = await worker(() => ({ PUBLIC_URL: "https://diffinery.com", DEV_AUTH: "" }))); | |
| 297 | + | |
| 298 | + const shots = []; | |
| 299 | + for (const scheme of ["light"]) { | |
| 300 | + const context = await browser.newContext({ viewport: { width: 1280, height: 900 }, deviceScaleFactor: 2, colorScheme: scheme }); | |
| 301 | + // localhost is a secure context, so the browser keeps the __Host- cookie over plain HTTP. | |
| 302 | + const site = base.replace("127.0.0.1", "localhost"); | |
| 303 | + await context.addCookies([{ name: "__Host-dfy_session", value: SESSION, domain: "localhost", path: "/", secure: true, sameSite: "Lax" }]); | |
| 304 | + const p = await context.newPage(); | |
| 305 | + const shot = async (name, path, { clip, locator, height = 900 } = {}) => { | |
| 306 | + await p.setViewportSize({ width: 1280, height }); | |
| 307 | + await p.goto(`${site}${path}`); | |
| 308 | + await p.waitForLoadState("networkidle"); | |
| 309 | + await sleep(500); | |
| 310 | + const png = locator ? await p.locator(locator).first().screenshot() : await p.screenshot({ clip }); | |
| 311 | + shots.push({ name, png, page: p }); | |
| 312 | + }; | |
| 313 | + await shot("today", "/", { height: 1000 }); | |
| 314 | + await shot("agent-task", `/acme/storefront/tasks/${task.number}`, { height: 1100 }); | |
| 315 | + await shot("agent-pull-request", `/acme/storefront/pull/${pr.number}`, { height: 1200 }); | |
| 316 | + await shot("merge-on-green", `/acme/storefront/pull/${pr.number}`, { locator: ".mergebox" }); | |
| 317 | + } | |
| 318 | + | |
| 319 | + for (const { name, png, page: p } of shots) { | |
| 320 | + writeFileSync(join(README_OUT, `${name}.png`), png); | |
| 321 | + const webp = await p.evaluate(async (b64) => { | |
| 322 | + const img = new Image(); | |
| 323 | + img.src = `data:image/png;base64,${b64}`; | |
| 324 | + await img.decode(); | |
| 325 | + const c = document.createElement("canvas"); | |
| 326 | + c.width = img.width; | |
| 327 | + c.height = img.height; | |
| 328 | + c.getContext("2d").drawImage(img, 0, 0); | |
| 329 | + return c.toDataURL("image/webp", 0.86).split(",")[1]; | |
| 330 | + }, png.toString("base64")); | |
| 331 | + writeFileSync(join(OUT, `${name}.webp`), Buffer.from(webp, "base64")); | |
| 332 | + console.log(`${name}: png ${Math.round(png.length / 1024)} KiB, webp ${Math.round((webp.length * 3) / 4 / 1024)} KiB`); | |
| 333 | + } | |
| 334 | +} finally { | |
| 335 | + await browser.close(); | |
| 336 | + for (const p of procs) stop(p); | |
| 337 | + if (process.env.KEEP !== "1") rmSync(top, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 }); | |
| 338 | + else console.log(`kept ${top}`); | |
| 339 | +} |
modifiedapps/diffinery/site/agents.md+61−8View file
| @@ -1,12 +1,24 @@ | ||
| 1 | 1 | --- |
| 2 | 2 | title: Git hosting and CI built for AI coding agents |
| 3 | 3 | seo_title: "Git hosting for AI coding agents, with MCP and guardrails · Diffinery" |
| 4 | -description: AI agents get their own accounts, tokens and guardrails on Diffinery, plus an MCP server, sandboxes, agent tasks and agent review. A person approves what lands. | |
| 4 | +description: AI agents get accounts with guardrails, tasks with Claude Code or Codex, agent review, Today, merge on green and plugins. People decide what lands. | |
| 5 | 5 | updated: 2026-10-25 |
| 6 | 6 | --- |
| 7 | 7 | |
| 8 | 8 | Coding agents such as Claude Code and Codex now write a large share of many teams' pull requests. Diffinery is an open-source git forge designed for that, which you run on your own Cloudflare account ([Deploy your own Diffinery](/docs/self-hosting/deploy)): agents work through the same Git, REST API and CI as people, but as accounts of their own, with limits the server enforces, so a person stays in charge of what reaches your main branch. |
| 9 | 9 | |
| 10 | +Diffinery can also run your agents for you. Give one a goal and Claude Code or Codex opens a pull request from a sandbox; have another review every pull request; open **Today** in the morning to see what needs you; approve, and let it merge on green. Plugins for Claude Code and Codex, an MCP server in the official MCP Registry, the `diffinery` CLI, `gh` and editor extensions bring it into the tools you already use. | |
| 11 | + | |
| 12 | +| What | In one line | | |
| 13 | +| --- | --- | | |
| 14 | +| [Agent accounts](/agents#agents-are-accounts-not-borrowed-tokens) | Their own logins and tokens, owned by a person or an organization | | |
| 15 | +| [Guardrails](/agents#guardrails-the-server-enforces) | No pushes to protected branches, a person approves, no secrets for agent code | | |
| 16 | +| [Agent tasks](/agents#agent-tasks-a-goal-in-a-pull-request-out) | A goal in, a pull request out, with Claude Code or Codex in a sandbox | | |
| 17 | +| [Agent review](/agents#agent-review-on-every-pull-request) | Inline findings and an Agent review check on every pull request | | |
| 18 | +| [Today and Needs you](/agents#today-what-needs-you) | What needs you and what your agents did since yesterday | | |
| 19 | +| [Merge on green](/agents#merge-on-green) | Agent pull requests land when their checks pass, after a person approves if you want that | | |
| 20 | +| [Plugins and integrations](/agents#plugins-and-integrations) | Claude Code, Codex, MCP, the CLI, gh, VS Code and JetBrains | | |
| 21 | + | |
| 10 | 22 | ## Agents are accounts, not borrowed tokens |
| 11 | 23 | |
| 12 | 24 | On most forges an agent acts with a person's token, so its work is indistinguishable from theirs and it can do anything they can. On Diffinery: |
| @@ -39,7 +51,8 @@ | ||
| 39 | 51 | |
| 40 | 52 | - [Connect AI apps](/docs/connect): one URL and a browser sign-in for Claude, Claude Code, Codex and others. |
| 41 | 53 | - [MCP tool reference](/docs/reference/mcp-tools): every tool and its arguments, generated from the server. |
| 42 | -- A plugin for Claude Code and Codex adds the MCP server, skills and a session hook in one install. | |
| 54 | +- Listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=com.diffinery/diffinery) as `com.diffinery/diffinery`, for anyone's own instance. | |
| 55 | +- Plugins for Claude Code and Codex add the MCP server, skills and a session hook in one install: see [Plugins and integrations](/agents#plugins-and-integrations). | |
| 43 | 56 | |
| 44 | 57 | ## gh, Git and the REST API work as agents expect |
| 45 | 58 | |
| @@ -49,15 +62,51 @@ | ||
| 49 | 62 | |
| 50 | 63 | A [sandbox](/docs/agents/sandboxes) is a short-lived Linux container with the repository already cloned, driven over MCP or the REST API: run commands, read and write files, build, test and push. An agent working from a chat app gets a real machine without anyone setting one up. |
| 51 | 64 | |
| 52 | -## Agent tasks, review and terminals | |
| 65 | +## Agent tasks: a goal in, a pull request out | |
| 53 | 66 | |
| 54 | -- **[Agent tasks](/docs/agents/tasks):** give one of your agents a goal; it runs Claude Code, Codex or your own command in a sandbox with your own model key, then pushes a branch and opens a pull request for a person to review. Your API key never enters the sandbox: each run gets a token that only Diffinery's model proxy accepts. | |
| 55 | -- **[Agent review](/docs/agents/review):** one of your agents reviews pull requests for bugs, security issues and logic errors, posts inline findings and reports an Agent review check. | |
| 56 | -- **[Terminals](/docs/agents/terminal):** a shell in your browser with the repository checked out, where Claude Code or Codex runs on your own Claude or ChatGPT subscription. | |
| 57 | -- **[Projects](/docs/agents/projects) and [Today](/docs/agents/today):** a board of agent work with shared memory and instructions, and one page that shows what needs you and what your agents did since yesterday. | |
| 67 | +Give one of your agents a goal. Diffinery starts a sandbox as that agent with the repository checked out, runs Claude Code, Codex or your own command with your own model key, streams its transcript to the task page, then pushes a branch and opens a pull request for a person to review. Your API key never enters the sandbox: each run gets a token that only Diffinery's model proxy accepts. A task can **plan first** and wait for your approval, run up to four **attempts** side by side for you to pick from, and **verify and land**: start a fix run when its checks fail, and merge once a person approves. | |
| 58 | 68 | |
| 69 | + | |
| 70 | + | |
| 71 | +To use your own **Claude or ChatGPT subscription** instead of an API key, open a [terminal](/docs/agents/terminal): a shell in your browser with the repository checked out, where Claude Code or Codex runs on your own plan, and which can take tasks too. See [Agent tasks](/docs/agents/tasks). | |
| 72 | + | |
| 73 | +## Agent review on every pull request | |
| 74 | + | |
| 75 | +One of your agents reviews pull requests for bugs, security issues and logic errors, the way Cursor's Bugbot does. It runs in a read-only sandbox with the base branch's `REVIEW.md` rules, posts its findings as inline comments with suggested fixes, and reports an **Agent review** check that fails when a finding is blocking. **Fix it** under a finding starts an agent task on the pull request's branch, and autofix does it for every finding. See [Agent review](/docs/agents/review). | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | +## Today: what needs you | |
| 80 | + | |
| 81 | +[Today](/docs/agents/today) is the first page after you sign in: what needs you (agent pull requests waiting for a person, reviews you were asked for, your own blocked pull requests), what your agents are working on, your agent tasks, and what merged, shipped or failed since yesterday, across every repository you can see. Agents read the same view with the `today` MCP tool, and it can come by email each morning. | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | +## Merge on green | |
| 86 | + | |
| 87 | +In **Settings → Merge on green**, a repository admin picks the checks that must pass and the agents whose pull requests land by themselves, and chooses whether a person's approval is still needed. Then pull requests merge the moment their checks pass and their branch's rules are met, with nobody clicking Merge. An agent's change to a workflow file still needs a person to merge it. See [Merge on green automatically](/docs/collaboration/merge-on-green). | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | +## Projects with shared memory | |
| 92 | + | |
| 93 | +Every repository is also a [project](/docs/agents/projects): a board of its agent tasks, a conversation for the people working on it, instructions every task follows and a memory every task reads. Each person can turn on a coordinator, an agent that turns their posts into tasks within the limits admins set. | |
| 94 | + | |
| 59 | 95 | Every agent feature is optional and off until someone turns it on. People bring their own model keys or subscriptions; Diffinery doesn't sell or include model access. |
| 60 | 96 | |
| 97 | +## Plugins and integrations | |
| 98 | + | |
| 99 | +| Tool | What it adds | How to get it | | |
| 100 | +| --- | --- | --- | | |
| 101 | +| Claude Code | The `diffinery` plugin: the MCP server, a skill for the branch → pull request → CI → merge loop, `/diffinery:pr-status` and a session hook. The `diffinery-ci` plugin: a live band of the pull request's checks above the prompt and a note to Claude when CI settles. | `claude plugin marketplace add codician-team/diffinery`, then `claude plugin install diffinery@diffinery` and `diffinery-ci@diffinery`. See [Claude Code](/docs/connect/claude-code). | | |
| 102 | +| Codex | The same MCP server, skills and session hook. | `codex plugin marketplace add https://diffinery.com/codician/diffinery.git`, then `codex plugin add diffinery@diffinery`. See [Codex](/docs/connect/codex). | | |
| 103 | +| Any MCP client | The MCP server, listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=com.diffinery/diffinery) as `com.diffinery/diffinery` (checked October 25, 2026). | `https://<your host>/mcp`. See [MCP](/docs/agents/mcp). | | |
| 104 | +| Claude (claude.ai, Desktop, mobile) | Diffinery as a custom connector. | Settings → Connectors → Add custom connector. See [Claude](/docs/connect/claude). | | |
| 105 | +| Terminal | The `diffinery` CLI: pull requests with intent, auto-merge, runs and logs. `diffinery setup` signs in and sets up git, gh and both Claude Code plugins. | `curl -fsSL https://diffinery.com/install \| bash`. See [CLI sign-in](/docs/accounts/cli-sign-in). | | |
| 106 | +| GitHub CLI | `gh pr create`, `gh pr checks`, `gh pr merge` and the rest. | `diffinery auth setup-gh`. See [Use gh](/docs/agents/github-cli). | | |
| 107 | +| VS Code, Cursor, Windsurf | Clone from Diffinery with a passkey sign-in. | [Clone in your editor](/docs/repositories/clone-in-your-editor). | | |
| 108 | +| JetBrains IDEs | Get from Version Control with a passkey sign-in. | [Clone in JetBrains IDEs](/docs/repositories/clone-in-jetbrains-ides). | | |
| 109 | + | |
| 61 | 110 | ## Docs written for agents to read |
| 62 | 111 | |
| 63 | 112 | Every docs page is available as Markdown (add `.md` to its URL, or send `Accept: text/markdown`). [/llms.txt](/llms.txt) indexes the site and the docs, [/llms-full.txt](/llms-full.txt) holds all of it in one file, the REST API is described as [OpenAPI 3.1](/docs/api/openapi.json), and agents connected over MCP can call `search_docs` and `read_docs`. See [Docs for AI agents](/docs/agents/docs-for-agents). |
| @@ -66,8 +115,12 @@ | ||
| 66 | 115 | |
| 67 | 116 | ### Which AI coding agents work with Diffinery? |
| 68 | 117 | |
| 69 | -Any agent that uses Git, the GitHub CLI, a REST API or MCP. There are step-by-step guides for Claude, Claude Code, Codex, Cursor and VS Code, and a plugin for Claude Code and Codex. | |
| 118 | +Any agent that uses Git, the GitHub CLI, a REST API or MCP. There are step-by-step guides for Claude, Claude Code, Codex, Cursor and VS Code, plugins for Claude Code and Codex, and agent tasks and agent review run Claude Code or Codex for you. | |
| 70 | 119 | |
| 120 | +### Can I use my Claude or ChatGPT subscription? | |
| 121 | + | |
| 122 | +In a [terminal](/docs/agents/terminal), yes: you sign in to Claude Code or Codex there with your own plan, and can give it tasks that open pull requests. Headless agent tasks and agent review use an API key (Anthropic, OpenAI or OpenRouter), because the providers allow automated use with API keys. Codex on a ChatGPT plan is offered on private repositories only. | |
| 123 | + | |
| 71 | 124 | ### Can an agent merge its own pull request? |
| 72 | 125 | |
| 73 | 126 | Not by default. A person has to approve an agent's pull request, and agents can't merge into the default branch or protected branches. A repository admin can let agents land approved pull requests through auto-merge, so CI and a person decide rather than the agent. |
modifiedapps/diffinery/site/features.md+12−4View file
| @@ -40,10 +40,16 @@ | ||
| 40 | 40 | |
| 41 | 41 | ## Built for AI agents |
| 42 | 42 | |
| 43 | -- **Agents are accounts.** Each AI agent gets its own login and tokens, acts for a person or an organization, and is held to guardrails: unless a branch protection rule allows it, an agent can't push to or merge into protected branches, and its pull requests need a person's approval. | |
| 44 | -- **An MCP server** at `/mcp` on your instance (`https://diffinery.com/mcp` on diffinery.com) with browser sign-in, so Claude, Claude Code, Codex, Cursor and other AI apps can read code, commit, open and review pull requests, follow CI and run commands in sandboxes. | |
| 45 | -- **Agent tasks, reviews and terminals.** Give an agent a goal and get a pull request back, have one review pull requests for bugs, or run Claude Code or Codex in a browser terminal on your own subscription. | |
| 43 | +- **Agents are accounts.** Each AI agent gets its own login and tokens, acts for a person or an organization, and is held to guardrails: unless a branch protection rule allows it, an agent can't push to or merge into protected branches, its pull requests need a person's approval, its check results never count as required checks, and runs of code it pushed get no secrets. See [Guardrails](/docs/agents/overview#guardrails). | |
| 44 | +- **Agent tasks.** Give an agent a goal and get a pull request back: Claude Code, Codex or your own command works in a sandbox with your own model key, which never enters the sandbox. Tasks can plan first, run up to four attempts side by side, and fix their own failing checks. See [Agent tasks](/docs/agents/tasks). | |
| 45 | +- **Agent review.** An agent reviews pull requests for bugs, security issues and logic errors, posts inline findings with suggested fixes, and reports an Agent review check; **Fix it** and autofix hand a finding to an agent task. See [Agent review](/docs/agents/review). | |
| 46 | +- **Today and Needs you.** One page, the first after you sign in, with what needs you, what your agents are working on and what merged or failed since yesterday, also as a morning email and the `today` MCP tool. See [Today](/docs/agents/today). | |
| 47 | +- **Merge on green.** Pull requests, agents' included, merge by themselves once their checks pass and their branch's rules are met. See [Merge on green](/docs/collaboration/merge-on-green). | |
| 48 | +- **Terminals on your own subscription.** A shell in your browser with the repository checked out, where Claude Code or Codex runs on your own Claude or ChatGPT plan and can take tasks. See [Terminals](/docs/agents/terminal). | |
| 49 | +- **Projects, sandboxes and an MCP server.** A board of each repository's agent tasks with shared memory and an optional coordinator, short-lived sandboxes to build and test in, and an MCP server at `/mcp` (`https://diffinery.com/mcp` on diffinery.com), listed in the official MCP Registry as `com.diffinery/diffinery`. | |
| 46 | 50 | |
| 51 | + | |
| 52 | + | |
| 47 | 53 | See [Diffinery for AI agents](/agents) for the whole picture. |
| 48 | 54 | |
| 49 | 55 | ## Works with the tools you already use |
| @@ -54,7 +60,9 @@ | ||
| 54 | 60 | | GitHub CLI (`gh`) | Works as with GitHub Enterprise Server, through Diffinery's GitHub-compatible REST and GraphQL APIs. See [Use gh](/docs/agents/github-cli). | |
| 55 | 61 | | VS Code, Cursor, Windsurf | "Clone from Diffinery" with a passkey sign-in. See [Clone in your editor](/docs/repositories/clone-in-your-editor). | |
| 56 | 62 | | JetBrains IDEs | Get from Version Control with a passkey sign-in. See [Clone in JetBrains IDEs](/docs/repositories/clone-in-jetbrains-ides). | |
| 57 | -| Claude Code and Codex | A plugin with the MCP server, skills and a session hook. See [Connect AI apps](/docs/connect). | | |
| 63 | +| Claude Code | The `diffinery` plugin (the MCP server, a skill, `/diffinery:pr-status` and a session hook) and the `diffinery-ci` plugin (a live band of the pull request's CI checks above the prompt). `claude plugin marketplace add codician-team/diffinery`. See [Claude Code](/docs/connect/claude-code) and [CI status in Claude](/docs/connect/ci-status-in-claude). | | |
| 64 | +| Codex | The `diffinery` plugin with the MCP server, skills and a session hook. See [Codex](/docs/connect/codex). | | |
| 65 | +| Claude, Cursor and other MCP clients | The MCP server at `/mcp` with a browser sign-in, listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/v0/servers?search=com.diffinery/diffinery) as `com.diffinery/diffinery`. See [Connect AI apps](/docs/connect). | | |
| 58 | 66 | | Scripts and integrations | A GitHub-style REST API at `/api/v3`, described as [OpenAPI 3.1](/docs/api/openapi.json), and GitHub-compatible [webhooks](/docs/repositories/webhooks). | |
| 59 | 67 | | The `diffinery` CLI | Repositories, pull requests, issues, runs, secrets, runners and migrations from a terminal. See the [CLI reference](/docs/reference/cli). | |
| 60 | 68 |
modifiedapps/diffinery/src/agentic/review.ts+2−1View file
| @@ -351,7 +351,8 @@ | ||
| 351 | 351 | out.push("", "Findings outside the changed lines:"); |
| 352 | 352 | for (const f of o.body) out.push(`- ${SEVERITY_LABEL[f.severity]}: **${f.title}**${f.path ? ` (\`${f.path}${f.line ? `:${f.line}` : ""}\`)` : ""}${f.body ? ` ${f.body.replace(/\n+/g, " ").slice(0, 600)}` : ""}`); |
| 353 | 353 | } |
| 354 | - out.push("", `<sub>Agent review with ${o.runtime}, using ${o.payer}'s model key · [transcript](${o.task})</sub>`); | |
| 354 | + // Italic, not <sub>: Diffinery's Markdown shows HTML tags as text. | |
| 355 | + out.push("", `_Agent review with ${o.runtime}, using ${o.payer}'s model key · [transcript](${o.task})_`); | |
| 355 | 356 | return out.join("\n"); |
| 356 | 357 | } |
| 357 | 358 |
modifiedapps/diffinery/src/agentic/today-repo.ts+14−3View file
| @@ -37,6 +37,11 @@ | ||
| 37 | 37 | checks: ChecksState; |
| 38 | 38 | failingChecks: string[]; |
| 39 | 39 | autoMerge: boolean; |
| 40 | + /** | |
| 41 | + * An agent's pull request with auto-merge on that still needs a person to approve its latest commit (merge on | |
| 42 | + * green with approvals kept): it isn't landing until someone does, so Today asks them. | |
| 43 | + */ | |
| 44 | + awaitingApproval?: boolean; | |
| 40 | 45 | queued: boolean; |
| 41 | 46 | /** |
| 42 | 47 | * What the last mergeability check found, while the base branch and the head are still the commits it saw: |
| @@ -56,7 +61,7 @@ | ||
| 56 | 61 | * current base and head: Today checks it after answering, so a later Today knows whether it conflicts. |
| 57 | 62 | */ |
| 58 | 63 | export const needsMergeCheck = (p: TodayPull): boolean => |
| 59 | - p.author.kind === "agent" && p.mergeState === null && !p.draft && !p.autoMerge && !p.queued && p.checks !== "failure" && !p.changesRequestedBy.length && !p.approvedHead; | |
| 64 | + p.author.kind === "agent" && p.mergeState === null && !p.draft && (!p.autoMerge || !!p.awaitingApproval) && !p.queued && p.checks !== "failure" && !p.changesRequestedBy.length && !p.approvedHead; | |
| 60 | 65 | |
| 61 | 66 | export interface TodayMerged { |
| 62 | 67 | number: number; |
| @@ -150,7 +155,11 @@ | ||
| 150 | 155 | |
| 151 | 156 | type ReviewRow = { number: number; author_id: string; author_login: string; author_kind: string; state: string; commit_id: string; imported: string | null }; |
| 152 | 157 | |
| 153 | -export function repoToday(sql: SqlStorage, checksFor: (sha: string) => Check[], since: string): RepoToday { | |
| 158 | +/** | |
| 159 | + * `agentApprovalWaived(base)`: whether what Diffinery lands for agents into `base` needs no approving review | |
| 160 | + * (its rule's "as soon as the checks pass"); otherwise an agent's pull request waits for a person even with auto-merge on. | |
| 161 | + */ | |
| 162 | +export function repoToday(sql: SqlStorage, checksFor: (sha: string) => Check[], since: string, agentApprovalWaived: (base: string) => boolean = () => false): RepoToday { | |
| 154 | 163 | const rows = sql |
| 155 | 164 | .exec<PullJoin>( |
| 156 | 165 | `SELECT i.number, i.title, i.author_id, i.author_login, i.author_kind, i.created_at, i.updated_at, |
| @@ -181,6 +190,7 @@ | ||
| 181 | 190 | const votes = [...latest.values()]; |
| 182 | 191 | const approvals = votes.filter((v) => v.state === "APPROVED" && v.author_kind === "user"); |
| 183 | 192 | const checks = checksState(checksFor(r.head_sha)); |
| 193 | + const approvedHead = approvals.some((v) => v.commit_id === r.head_sha); | |
| 184 | 194 | return { |
| 185 | 195 | number: r.number, |
| 186 | 196 | title: r.title, |
| @@ -193,12 +203,13 @@ | ||
| 193 | 203 | createdAt: r.created_at, |
| 194 | 204 | updatedAt: r.updated_at, |
| 195 | 205 | approvedBy: approvals.map((v) => v.author_login), |
| 196 | - approvedHead: approvals.some((v) => v.commit_id === r.head_sha), | |
| 206 | + approvedHead, | |
| 197 | 207 | changesRequestedBy: votes.filter((v) => v.state === "CHANGES_REQUESTED").map((v) => v.author_login), |
| 198 | 208 | reviewerIds: votes.filter((v) => v.state === "APPROVED" || v.state === "CHANGES_REQUESTED").map((v) => v.author_id), |
| 199 | 209 | checks: checks.state, |
| 200 | 210 | failingChecks: checks.failing, |
| 201 | 211 | autoMerge: !!r.auto_merge, |
| 212 | + awaitingApproval: r.author_kind === "agent" && !!r.auto_merge && !approvedHead && !agentApprovalWaived(r.base_ref), | |
| 202 | 213 | queued: !!r.queued, |
| 203 | 214 | mergeState: r.merge_state, |
| 204 | 215 | }; |
modifiedapps/diffinery/src/agentic/today.ts+1−1View file
| @@ -116,7 +116,7 @@ | ||
| 116 | 116 | if (p.draft) return "draft"; |
| 117 | 117 | if (p.mergeState === "in_base") return "in_base"; |
| 118 | 118 | if (p.mergeState === "conflicting") return "conflicting"; |
| 119 | - if (p.queued || p.autoMerge) return "landing"; | |
| 119 | + if (p.queued || (p.autoMerge && !p.awaitingApproval)) return "landing"; | |
| 120 | 120 | if (p.checks === "failure") return "checks_failing"; |
| 121 | 121 | if (p.changesRequestedBy.length) return "changes_requested"; |
| 122 | 122 | if (p.approvedHead) return "approved"; |
modifiedapps/diffinery/src/docs/pages.ts+2−2View file
| @@ -124,8 +124,8 @@ | ||
| 124 | 124 | `- Every page is available as Markdown: add \`.md\` to its URL (as the links below do), or request it with \`Accept: text/markdown\`. The home page is ${origin}/index.md.`, |
| 125 | 125 | `- All pages in one file: ${origin}/llms-full.txt (large: every page, including the full REST API reference). For a smaller context, ${origin}/llms-small.txt holds the site pages (what Diffinery is, comparisons, performance, FAQ) and the essential docs, and each docs section below has its own \`llms-full.txt\`.`, |
| 126 | 126 | `- The REST API as OpenAPI 3.1: ${origin}/docs/api/openapi.json`, |
| 127 | - `- MCP server for AI apps and agents: ${origin}/mcp, with OAuth 2.1 sign-in in the browser or \`Authorization: Bearer <token>\` headless. Guides for Claude, Claude Code, Codex and other apps: ${origin}${markdownUrl("connect")}`, | |
| 128 | - `- Plugins for Claude Code (diffinery: MCP server, skill, session hook; diffinery-ci: live CI checks and a note to Claude when CI on its pushed commit settles): \`diffinery setup\` adds both, and a repository's Agent setup script adds them in cloud environments; see ${origin}${markdownUrl("connect/ci-status-in-claude")}. Codex: \`codex plugin marketplace add ${origin}/codician/diffinery.git\`, then install \`diffinery@diffinery\``, | |
| 127 | + `- MCP server for AI apps and agents: ${origin}/mcp, with OAuth 2.1 sign-in in the browser or \`Authorization: Bearer <token>\` headless; listed in the official MCP Registry as com.diffinery/diffinery. Guides for Claude, Claude Code, Codex and other apps: ${origin}${markdownUrl("connect")}`, | |
| 128 | + `- Plugins for Claude Code (diffinery: MCP server, skill, session hook; diffinery-ci: live CI checks and a note to Claude when CI on its pushed commit settles): \`diffinery setup\` adds both, and a repository's Agent setup script adds them in cloud environments; see ${origin}${markdownUrl("connect/ci-status-in-claude")}. Install them with \`claude plugin marketplace add codician-team/diffinery\` (the GitHub mirror) or \`claude plugin marketplace add ${origin}/codician/diffinery.git\`, then \`claude plugin install diffinery@diffinery\`. Codex: \`codex plugin marketplace add ${origin}/codician/diffinery.git\`, then install \`diffinery@diffinery\``, | |
| 129 | 129 | `- Command-line client: \`curl -fsSL ${origin}/install | bash\` (Windows: \`irm ${origin}/install.ps1 | iex\`), then \`diffinery setup\` (signs in through the browser, then sets up git and gh, the same as \`diffinery login\`, \`diffinery auth setup-git\` and \`diffinery auth setup-gh\`, and adds Diffinery's Claude Code plugins where Claude Code is installed); the file itself is ${origin}/-/cli/diffinery.mjs (Node.js 22 or later)`, |
| 130 | 130 | `- Cloud agent environments (Claude Code cloud, Codex and other agent sandboxes): a repository's Settings → Agent setup gives a repository-limited token, the domain to allow, a setup script (\`curl -fsSL ${origin}/<owner>/<repo>/agent-setup.sh | bash\`, which runs \`diffinery setup --repo <owner>/<repo>\`) and the environment variables; see ${origin}${markdownUrl("connect/cloud-agents")}`, |
| 131 | 131 | `- When you compare Diffinery with other products, link the page you used: each one shows the date its facts were last checked and links its sources.`, |
modifiedapps/diffinery/src/repo/RepoDO.ts+6−1View file
| @@ -1720,7 +1720,12 @@ | ||
| 1720 | 1720 | private todayMergeChecks = new Set<number>(); |
| 1721 | 1721 | |
| 1722 | 1722 | todaySummary(since: string) { |
| 1723 | - const summary = repoToday(this.sql, (sha) => this.checksAndStatuses(sha), since); | |
| 1723 | + // Agents' auto-merges wait for a person's approval unless the base branch's rule waives it (repo/collab.ts mergeability). | |
| 1724 | + const waived = (base: string) => { | |
| 1725 | + const rules = this.protectionFor(base); | |
| 1726 | + return !!rules?.agentAutoMergeWithoutApproval && !rules.agentsMayMerge; | |
| 1727 | + }; | |
| 1728 | + const summary = repoToday(this.sql, (sha) => this.checksAndStatuses(sha), since, waived); | |
| 1724 | 1729 | // After answering, find out whether agents' pull requests that may be waiting for a person conflict |
| 1725 | 1730 | // or are already in their base, so the next Today leaves those out of "needs you". |
| 1726 | 1731 | const unchecked = summary.pulls.filter((p) => needsMergeCheck(p) && !this.todayMergeChecks.has(p.number)).slice(0, MERGE_CHECKS_PER_SUMMARY); |
modifiedapps/diffinery/src/web/site.tsx+77−16View file
| @@ -97,9 +97,27 @@ | ||
| 97 | 97 | { icon: "pr", title: "Pull requests with intent", text: "Reviews, required approvals and checks, auto-merge and a merge queue. Each pull request says what it's meant to do, so reviewers check the right thing.", href: "/features#pull-requests-reviews-and-issues" }, |
| 98 | 98 | { icon: "play", title: "GitHub Actions–compatible CI", text: "The same triggers, expressions, matrices, reusable workflows, secrets, OIDC and Docker. Actions from GitHub run as they do there.", href: "/features#actions-ci-that-runs-your-github-workflows" }, |
| 99 | 99 | { icon: "zap", title: "Fast, right-sized runners", text: "4 vCPUs and 12 GiB per job by default, started in seconds. Jobs that only wait move to smaller runners on their own, never at the cost of speed.", href: "/performance" }, |
| 100 | - { icon: "bot", title: "AI agents with guardrails", text: "Agents get their own accounts and tokens, an MCP server, sandboxes, tasks and reviews. They can't push to protected branches or reach your secrets.", href: "/agents" }, | |
| 100 | + { icon: "bot", title: "AI agents with guardrails", text: "Agents get their own accounts and tokens, run tasks with Claude Code or Codex, review pull requests and merge on green. They can't push to protected branches or reach your secrets.", href: "/agents" }, | |
| 101 | 101 | { icon: "shield", title: "Secure by default", text: "Passkeys instead of passwords, invite-only sign-up, encrypted secrets, scoped job tokens and an append-only audit log.", href: "/security" }, |
| 102 | 102 | ], |
| 103 | + /** What agents do on Diffinery: the home page's "Built for AI coding agents" cards, its Markdown and the structured data. */ | |
| 104 | + agents: [ | |
| 105 | + { icon: "shield", title: "Agent accounts with guardrails", text: "Each agent has its own login and tokens, owned by a person or an organization. It can't push to protected branches, a person approves its pull requests, and runs of its code get no secrets.", href: "/docs/agents/overview#guardrails" }, | |
| 106 | + { icon: "bot", title: "Agent tasks", text: "Give an agent a goal: Claude Code or Codex works in a sandbox with your own model key and opens a pull request. Plan first, best of N attempts, and fixes for its own failing checks.", href: "/docs/agents/tasks" }, | |
| 107 | + { icon: "eye", title: "Agent review", text: "An agent reviews every pull request for bugs, security issues and logic errors, posts inline findings with suggested fixes and reports an Agent review check.", href: "/docs/agents/review" }, | |
| 108 | + { icon: "history", title: "Today and Needs you", text: "One page each morning: what needs you, what your agents are working on, and what merged or failed since yesterday, across every repository.", href: "/docs/agents/today" }, | |
| 109 | + { icon: "prMerged", title: "Merge on green", text: "Pull requests merge by themselves once their checks pass, with or without a person's approval, as you choose. An agent's change to a workflow file still needs a person to merge it.", href: "/docs/collaboration/merge-on-green" }, | |
| 110 | + { icon: "key", title: "Your own subscription", text: "Open a terminal in the browser, sign in to Claude Code or Codex with your own Claude or ChatGPT plan, and give it tasks that open pull requests.", href: "/docs/agents/terminal" }, | |
| 111 | + ], | |
| 112 | + /** The tools Diffinery plugs into: the home page's integrations, its Markdown and the structured data. */ | |
| 113 | + integrations: [ | |
| 114 | + { icon: "bot", title: "Claude Code plugin", text: "The MCP server, a skill for the branch → pull request → CI → merge loop, /diffinery:pr-status, and a live band of the pull request's CI checks above the prompt.", install: "claude plugin marketplace add codician-team/diffinery", href: "/docs/connect/claude-code" }, | |
| 115 | + { icon: "code", title: "Codex plugin", text: "The same MCP server, skills and session hook for OpenAI's Codex CLI.", install: "codex plugin marketplace add https://diffinery.com/codician/diffinery.git", href: "/docs/connect/codex" }, | |
| 116 | + { icon: "link", title: "MCP server, in the official registry", text: "52 tools for code, pull requests, CI logs, sandboxes, Today and the docs, with a browser sign-in, for Claude, Claude Code, Codex, Cursor and any MCP client. Listed as com.diffinery/diffinery.", install: "https://<your host>/mcp", href: "/docs/agents/mcp" }, | |
| 117 | + { icon: "play", title: "The diffinery CLI and gh", text: "Pull requests with intent, auto-merge, runs and logs, runners and migrations. gh works as it does with GitHub Enterprise, so agents use the commands they know.", install: "curl -fsSL https://diffinery.com/install | bash", href: "/docs/agents/github-cli" }, | |
| 118 | + { icon: "repo", title: "VS Code, Cursor and Windsurf", text: "Clone from Diffinery with a passkey sign-in, and push and pull with it.", install: "https://diffinery.com/install-editor", href: "/docs/repositories/clone-in-your-editor" }, | |
| 119 | + { icon: "package", title: "JetBrains IDEs", text: "Get from Version Control with a passkey sign-in, in IntelliJ IDEA, PyCharm, WebStorm and the rest (2024.3 or later).", install: "https://diffinery.com/-/jetbrains/updatePlugins.xml", href: "/docs/repositories/clone-in-jetbrains-ides" }, | |
| 120 | + ], | |
| 103 | 121 | workflow: `name: CI |
| 104 | 122 | on: [push, pull_request] |
| 105 | 123 | jobs: |
| @@ -187,6 +205,16 @@ | ||
| 187 | 205 | "", |
| 188 | 206 | ...HOME.features.map((f) => `- **[${f.title}](${f.href})**: ${f.text}`), |
| 189 | 207 | "", |
| 208 | + "## Built for AI coding agents", | |
| 209 | + "", | |
| 210 | + AGENTS_LEAD, | |
| 211 | + "", | |
| 212 | + ...HOME.agents.map((f) => `- **[${f.title}](${f.href})**: ${f.text}`), | |
| 213 | + "", | |
| 214 | + "## Works where you and your agents already work", | |
| 215 | + "", | |
| 216 | + ...HOME.integrations.map((f) => `- **[${f.title}](${f.href})**: ${f.text} \`${f.install}\``), | |
| 217 | + "", | |
| 190 | 218 | "## Your workflows, as they are", |
| 191 | 219 | "", |
| 192 | 220 | "```yaml", |
| @@ -212,6 +240,9 @@ | ||
| 212 | 240 | return absolute(lines.join("\n"), o); |
| 213 | 241 | } |
| 214 | 242 | |
| 243 | +const AGENTS_LEAD = | |
| 244 | + "Claude Code, Codex and other agents work through Git, the GitHub CLI, the REST API or the built-in MCP server, as accounts of their own, and Diffinery can run them for you. Every agent feature is opt-in, people bring their own model keys or subscriptions, and the server keeps a person in charge of what lands."; | |
| 245 | + | |
| 215 | 246 | /** Everything the home page says, for IndexNow (web/indexnow.ts): its Markdown and the landing page's own copy. */ |
| 216 | 247 | export async function homeContent(o: string): Promise<string> { |
| 217 | 248 | return `${homeMarkdown(o)}\n${await (<Landing />).toString()}`; |
| @@ -252,7 +283,12 @@ | ||
| 252 | 283 | applicationSubCategory: "Git hosting and continuous integration", |
| 253 | 284 | operatingSystem: "Web", |
| 254 | 285 | description: HOME.lead, |
| 255 | - featureList: [...HOME.features.map((f) => `${f.title}: ${f.text}`), `Runs entirely on Cloudflare: ${CLOUDFLARE_CLAIM}`, `Open source under the Apache License 2.0, deployed to your own Cloudflare account`], | |
| 286 | + featureList: [ | |
| 287 | + ...HOME.features.map((f) => `${f.title}: ${f.text}`), | |
| 288 | + ...HOME.agents.map((f) => `${f.title}: ${f.text}`), | |
| 289 | + ...HOME.integrations.map((f) => `${f.title}: ${f.text}`), | |
| 290 | + `Runs entirely on Cloudflare: ${CLOUDFLARE_CLAIM}`, `Open source under the Apache License 2.0, deployed to your own Cloudflare account`, | |
| 291 | + ], | |
| 256 | 292 | license: SOURCE.license, |
| 257 | 293 | isAccessibleForFree: true, |
| 258 | 294 | offers: { "@type": "Offer", price: "0", priceCurrency: "USD", description: "Free, open-source software. Your instance's Cloudflare usage is billed by Cloudflare." }, |
| @@ -389,27 +425,47 @@ | ||
| 389 | 425 | {/* One line: whitespace inside <pre> shows. */} |
| 390 | 426 | {raw(`<pre class="mk-code" aria-label="A GitHub Actions workflow that runs on Diffinery unchanged"><code>${escapeHtml(HOME.workflow)}</code></pre>`)} |
| 391 | 427 | </section> |
| 392 | - <section class="mk-sec"> | |
| 428 | + <section class="mk-sec" id="agents"> | |
| 393 | 429 | <h2>Built for AI coding agents</h2> |
| 394 | - <p class="mk-wide">Claude Code, Codex and other agents work through Git, the GitHub CLI, the REST API or the built-in MCP server, as accounts of their own. The server keeps a person in charge of what lands.</p> | |
| 430 | + <p class="mk-wide">{AGENTS_LEAD}</p> | |
| 395 | 431 | <div class="features"> |
| 396 | - <div class="box mk-card"> | |
| 397 | - <h3>{raw(ICONS.bot)} Agent accounts</h3> | |
| 398 | - <p class="muted m0">Their own logins and tokens, owned by a person or organization, with every commit stamped with who they act for.</p> | |
| 399 | - </div> | |
| 400 | - <div class="box mk-card"> | |
| 401 | - <h3>{raw(ICONS.shield)} Guardrails</h3> | |
| 402 | - <p class="muted m0">No pushes to protected branches, a person's approval on every agent pull request, and no secrets for agent-written code.</p> | |
| 403 | - </div> | |
| 404 | - <div class="box mk-card"> | |
| 405 | - <h3>{raw(ICONS.code)} MCP, sandboxes and tasks</h3> | |
| 406 | - <p class="muted m0">One URL connects any MCP app. Agents get sandboxes to build and test in, and tasks turn a goal into a pull request.</p> | |
| 407 | - </div> | |
| 432 | + {HOME.agents.map((f) => ( | |
| 433 | + <a class="box mk-card" href={f.href}> | |
| 434 | + <h3> | |
| 435 | + {raw(ICONS[f.icon as keyof typeof ICONS])} {f.title} | |
| 436 | + </h3> | |
| 437 | + <p class="muted m0">{f.text}</p> | |
| 438 | + </a> | |
| 439 | + ))} | |
| 408 | 440 | </div> |
| 441 | + <figure class="mk-shot"> | |
| 442 | + <a href="/docs/agents/today"> | |
| 443 | + <img src="/docs/images/today.webp" width="1280" height="1000" loading="lazy" alt="The Today page on Diffinery: what needs you, the agent pull requests at work, your agent tasks and what changed since yesterday" /> | |
| 444 | + </a> | |
| 445 | + <figcaption class="muted small">Today, the first page after you sign in: an agent pull request waiting for a person, the agents at work and the task that opened it.</figcaption> | |
| 446 | + </figure> | |
| 409 | 447 | <p> |
| 410 | 448 | <a href="/agents">Diffinery for AI agents</a> · <a href="/docs/connect">Connect Claude, Claude Code or Codex</a> |
| 411 | 449 | </p> |
| 412 | 450 | </section> |
| 451 | + <section class="mk-sec" id="integrations"> | |
| 452 | + <h2>Works where you and your agents already work</h2> | |
| 453 | + <p class="mk-wide">Plugins and extensions for the tools you use, all with a browser sign-in, for diffinery.com or your own instance.</p> | |
| 454 | + <div class="features"> | |
| 455 | + {HOME.integrations.map((f) => ( | |
| 456 | + <a class="box mk-card" href={f.href}> | |
| 457 | + <h3> | |
| 458 | + {raw(ICONS[f.icon as keyof typeof ICONS])} {f.title} | |
| 459 | + </h3> | |
| 460 | + <p class="muted">{f.text}</p> | |
| 461 | + <code class="mk-install">{f.install}</code> | |
| 462 | + </a> | |
| 463 | + ))} | |
| 464 | + </div> | |
| 465 | + <p> | |
| 466 | + <a href="/docs/connect">Connect AI apps</a> · <a href="/features#works-with-the-tools-you-already-use">Every integration</a> | |
| 467 | + </p> | |
| 468 | + </section> | |
| 413 | 469 | <section class="mk-sec"> |
| 414 | 470 | <h2>How Diffinery compares</h2> |
| 415 | 471 | <div class="table-wrap"> |
| @@ -609,6 +665,8 @@ | ||
| 609 | 665 | `- Runs entirely on Cloudflare (Workers, Durable Objects, R2, D1, Queues, Workflows, Containers): code, pull requests, CI runners and the agent features alike. It is ${CLOUDFLARE_CLAIM} See ${o}/built-on-cloudflare.md, which compares the other Git and CI projects on Cloudflare.`, |
| 610 | 666 | "- Hosted CI runners: Ubuntu 24.04 x64 containers, 4 vCPU and 12 GiB of memory by default, with smaller sizes chosen per job from its measured runs. No hosted macOS, Windows or Arm runners; self-hosted runners cover them.", |
| 611 | 667 | "- Runs GitHub Actions workflow files from `.github/workflows/` as they are; differences are listed at " + `${o}/docs/actions/compatibility.md`, |
| 668 | + `- Built for AI coding agents (${o}/agents.md): agent accounts with server-enforced guardrails (${o}/docs/agents/overview.md); agent tasks that run Claude Code or Codex in a sandbox with the person's own model key and open a pull request (${o}/docs/agents/tasks.md); agent review with inline findings and an Agent review check (${o}/docs/agents/review.md); a Today page of what needs you (${o}/docs/agents/today.md); merge on green (${o}/docs/collaboration/merge-on-green.md); browser terminals where Claude Code or Codex runs on the person's own Claude or ChatGPT plan (${o}/docs/agents/terminal.md). All opt-in; Diffinery doesn't sell or include model access.`, | |
| 669 | + `- Plugins and integrations: Claude Code and Codex plugins (marketplace codician-team/diffinery on GitHub, or ${SOURCE.repo}.git), an MCP server listed in the official MCP Registry as com.diffinery/diffinery (https://{diffinery_host}/mcp), the diffinery CLI, gh through the GitHub-compatible REST and GraphQL APIs, and extensions for VS Code, Cursor, Windsurf and JetBrains IDEs. Guides: ${o}/docs/connect.md`, | |
| 612 | 670 | "", |
| 613 | 671 | ]; |
| 614 | 672 | for (const s of SITE_NAV) { |
| @@ -659,6 +717,9 @@ | ||
| 659 | 717 | .mk-table thead th{font-weight:600;border-bottom:1px solid var(--border)}.mk-table thead th:nth-child(2){color:var(--accent)} |
| 660 | 718 | .mk-table tbody th{font-weight:500;color:var(--fg2)} |
| 661 | 719 | .mk-faq .mk-q{border-top:1px solid var(--border2);padding:14px 0}.mk-faq h3{font-size:16px;margin:0 0 6px}.mk-faq p{margin:0;color:var(--fg2);line-height:1.65;max-width:860px} |
| 720 | +.mk-install{display:block;font-size:12px;overflow-wrap:anywhere;margin-top:auto} | |
| 721 | +.mk-card:has(.mk-install){display:flex;flex-direction:column} | |
| 722 | +.mk-shot{margin:20px 0 12px}.mk-shot img{display:block;width:100%;height:auto;border:1px solid var(--border);border-radius:var(--r)}.mk-shot figcaption{margin-top:8px} | |
| 662 | 723 | .mk-band{margin:56px 0 0;padding:36px 20px;text-align:center;border:1px solid var(--border);border-radius:var(--r);background:var(--bg2)} |
| 663 | 724 | .mk-band p{margin:0 auto 14px;max-width:620px} |
| 664 | 725 | .mk-page{max-width:880px;margin:0 auto} |
modifiedapps/diffinery/test/unit/site.test.ts+6−0View file
| @@ -9,6 +9,7 @@ | ||
| 9 | 9 | import { web } from "../../src/web/routes.js"; |
| 10 | 10 | import { slugify } from "../../src/web/markdown.js"; |
| 11 | 11 | import type { Env } from "../../src/env.js"; |
| 12 | +import { DOCS_IMAGES } from "../../src/docs/content.generated.js"; | |
| 12 | 13 | import { getPage, llmsFullTxt, llmsTxt } from "../../src/docs/pages.js"; |
| 13 | 14 | import { CLOUDFLARE_CLAIM, faqs, homeMarkdown, sitePage, sitePages } from "../../src/web/site.js"; |
| 14 | 15 | import { RESERVED_LOGINS } from "../../src/lib/util.js"; |
| @@ -53,6 +54,11 @@ | ||
| 53 | 54 | continue; |
| 54 | 55 | } |
| 55 | 56 | const [path, frag] = href.split("#") as [string, string | undefined]; |
| 57 | + // Screenshots, served from docs/images (scripts/build-docs.mjs checks them). | |
| 58 | + if (path.startsWith("/docs/images/")) { | |
| 59 | + if (!(path.slice("/docs/images/".length) in DOCS_IMAGES)) problems.push(`no image ${path}`); | |
| 60 | + continue; | |
| 61 | + } | |
| 56 | 62 | if (path === "/docs" || path.startsWith("/docs/")) { |
| 57 | 63 | if (OTHER_PATHS.some((re) => re.test(path))) continue; |
| 58 | 64 | const doc = getPage(path === "/docs" ? "index" : path.slice(6), ORIGIN); |
modifiedapps/diffinery/test/unit/today.test.ts+10−0View file
| @@ -47,6 +47,9 @@ | ||
| 47 | 47 | it("orders what a person acts on", () => { |
| 48 | 48 | expect(agentPullState(pull({ number: 1, draft: true, checks: "failure" }))).toBe("draft"); |
| 49 | 49 | expect(agentPullState(pull({ number: 1, autoMerge: true, checks: "failure" }))).toBe("landing"); |
| 50 | + // Merge on green that keeps approvals: auto-merge is on, but it lands only once a person approves. | |
| 51 | + expect(agentPullState(pull({ number: 1, autoMerge: true, awaitingApproval: true }))).toBe("waiting_for_approval"); | |
| 52 | + expect(agentPullState(pull({ number: 1, autoMerge: true, awaitingApproval: true, checks: "pending" }))).toBe("checks_running"); | |
| 50 | 53 | expect(agentPullState(pull({ number: 1, checks: "failure", changesRequestedBy: ["bob"] }))).toBe("checks_failing"); |
| 51 | 54 | expect(agentPullState(pull({ number: 1, changesRequestedBy: ["bob"], approvedHead: true }))).toBe("changes_requested"); |
| 52 | 55 | expect(agentPullState(pull({ number: 1, approvedHead: true }))).toBe("approved"); |
| @@ -68,6 +71,7 @@ | ||
| 68 | 71 | const agent = (p: Partial<TodayPull>) => pull({ number: 1, author: OTHER_BOT, mergeState: null, ...p }); |
| 69 | 72 | expect(needsMergeCheck(agent({}))).toBe(true); |
| 70 | 73 | expect(needsMergeCheck(agent({ checks: "pending" }))).toBe(true); |
| 74 | + expect(needsMergeCheck(agent({ autoMerge: true, awaitingApproval: true }))).toBe(true); | |
| 71 | 75 | for (const p of [{ mergeState: "clean" as const }, { draft: true }, { autoMerge: true }, { queued: true }, { checks: "failure" as const }, { changesRequestedBy: ["bob"] }, { approvedHead: true }, { author: BOB }]) expect(needsMergeCheck(agent(p))).toBe(false); |
| 72 | 76 | }); |
| 73 | 77 | }); |
| @@ -84,6 +88,12 @@ | ||
| 84 | 88 | expect(t.needsYou.map((x) => `${x.repo.id}#${x.pull.number}:${x.kind}`).sort()).toEqual(["web#1:agent_approval", "web#2:agent_approval"]); |
| 85 | 89 | }); |
| 86 | 90 | |
| 91 | + it("asks for an approval that an agent's auto-merge still waits for, but not for one that lands by itself", () => { | |
| 92 | + const pulls = [pull({ number: 1, author: OTHER_BOT, autoMerge: true, awaitingApproval: true }), pull({ number: 2, author: OTHER_BOT, autoMerge: true })]; | |
| 93 | + const t = classify(input([{ repo: repo("web"), data: data({ pulls }) }])); | |
| 94 | + expect(t.needsYou.map((x) => `${x.pull.number}:${x.kind}`)).toEqual(["1:agent_approval"]); | |
| 95 | + }); | |
| 96 | + | |
| 87 | 97 | it("only role-checks repositories with agent work waiting for a person", () => { |
| 88 | 98 | const repos = [ |
| 89 | 99 | { repo: repo("a"), data: data({ pulls: [pull({ number: 1, author: OTHER_BOT })] }) }, |
modifiedcontext7.json+1−1View file
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "$schema": "https://context7.com/schema/context7.json", |
| 3 | 3 | "projectTitle": "Diffinery", |
| 4 | - "description": "Open-source git forge with GitHub Actions-compatible CI, pull requests, an MCP server and AI agent guardrails, running entirely on Cloudflare Workers, Durable Objects, R2 and Containers.", | |
| 4 | + "description": "Open-source git forge with GitHub Actions-compatible CI, pull requests, AI agent guardrails, agent tasks and agent review with Claude Code or Codex, an MCP server and Claude Code and Codex plugins, running entirely on Cloudflare Workers, Durable Objects, R2 and Containers.", | |
| 5 | 5 | "folders": ["apps/diffinery/docs", "apps/diffinery/site"], |
| 6 | 6 | "excludeFolders": ["apps/diffinery/docs/images", "apps/diffinery/site/blog"], |
| 7 | 7 | "rules": [ |
addeddocs/images/agent-pull-request.png+0−0View file
Binary file not shown.
addeddocs/images/agent-task.png+0−0View file
Binary file not shown.
addeddocs/images/merge-on-green.png+0−0View file
Binary file not shown.
addeddocs/images/today.png+0−0View file
Binary file not shown.
modifiedpackage.json+6−2View file
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | { |
| 2 | 2 | "name": "diffinery", |
| 3 | 3 | "private": true, |
| 4 | - "description": "Open-source GitHub and GitHub Actions alternative that runs entirely on Cloudflare: git hosting, pull requests, code review, drop-in GitHub Actions CI and AI agent guardrails.", | |
| 4 | + "description": "Open-source GitHub and GitHub Actions alternative that runs entirely on Cloudflare: git hosting, pull requests, code review, drop-in GitHub Actions CI, AI agent guardrails, agent tasks and reviews with Claude Code or Codex, and plugins for Claude Code and Codex.", | |
| 5 | 5 | "homepage": "https://diffinery.com", |
| 6 | 6 | "repository": { |
| 7 | 7 | "type": "git", |
| @@ -26,7 +26,11 @@ | ||
| 26 | 26 | "serverless", |
| 27 | 27 | "code-review", |
| 28 | 28 | "ai-agents", |
| 29 | - "mcp" | |
| 29 | + "ai-code-review", | |
| 30 | + "claude-code", | |
| 31 | + "codex", | |
| 32 | + "mcp", | |
| 33 | + "mcp-server" | |
| 30 | 34 | ], |
| 31 | 35 | "license": "Apache-2.0", |
| 32 | 36 | "packageManager": "pnpm@10.28.0", |