Skip to content

Repository files navigation

ADP — the agent-native forge

ADP is a self-hosted, GitHub-compatible forge for AI coding agents. Keep using git, gh, and existing CI integrations while ADP binds every change to its intent, agent provenance, approvals, and signed verification evidence.

Why ADP? An agent saying “tests pass” is not proof, and the transcript that could show its work is gone when the session ends. ADP keeps that context on the change itself, and refuses to land a change that does not meet your evidence requirements.

Try it · On a repository you already have · How it works · Self-hosting

New here? Start with the site — what ADP is, why it exists, and the AI-native SDLC stage by stage.

MIT · TypeScript · Fastify · PostgreSQL · the real git binary for all plumbing.


Try it

Two paths, answering different questions. The first shows you what ADP does; the second puts it under the work you already have. Take them in that order — the demo is much the better answer to the first question, and it takes a minute.

Neither creates an account. If you want to see the client without installing anything at all, npx @deduva/adp --help is the whole CLI and no tree — it talks to an instance, so one of the two paths below is what makes it do something.

1. Show me

One command. It starts a throwaway ADP against an ephemeral PostgreSQL, then uses ordinary git and an unmodified gh to clone, push, open a proposal, report a gate, and land the change — narrating each step. Nothing is installed, no account is created, and everything is torn down when you press Enter.

git clone https://github.com/DeDuva/adp.git && cd adp
make demo

It ends where the point is. The merge is refused while the change has no gate result and no approval, and allowed once it has both. You then read the signed evidence bundle and the operation log that record why.

Needs Docker and Node 22 (make doctor checks). About a minute, most of it the first npm ci.

Liked it and want one that is still there tomorrow?

make local     # then make local-down / make local-destroy

Same server, same proxy, longer lifetime: Postgres on a named volume, a self-signed certificate for localhost written where you can add it to your trust store, and the token printed. gh will talk to it, which plain HTTP never allows for a non-github.com host. It is for evaluation and development, not a deployment — docs/self-hosting.md §3b says exactly where that line is.

Or run the full verification suite

Every tier, including conformance against the real gh binary and the browser-driven acceptance walkthrough. Minutes rather than seconds, and it proves the parts a demo skips:

bash scripts/dev/bootstrap.sh
make up && make test-all && make down

2. Try it on the repository you already have

Companion mode: your repository stays on GitHub and you do not change how you work. Issues, branches, pull requests, reviews, Actions and the merge button all stay where they are. ADP sits underneath, records what each of them means, and publishes its verdict back onto the pull request as a check GitHub can require.

You need an instance to point at. make local above is enough — it prints a URL and a token, and it is still there tomorrow. Then, from inside your own checkout:

npx @deduva/adp login --server http://localhost:8420 --token <the token make local printed>
npx @deduva/adp init --repo local/<your-repo> --credential <a GitHub token ADP can push with>

Three things that save a wrong turn on a make local instance:

  • The CLI uses the plain HTTP port, not the TLS one. gh is the tool that needs the certificate, because it refuses plain HTTP for any host but github.com; adp has no such rule.
  • Name the org. adp init infers example-org/widget from your GitHub remote and your token is not a member of an org by that name, so it refuses and tells you to name one. On a make local instance the org you have is local.
  • The mirror needs a credential ADP can push with--credential, or GITHUB_TOKEN in the environment.

adp init creates the repository on your instance, records which repository this clone is, configures the mirror in both directions, and writes an adp.yaml detected from what your repository already says about itself — for a Node repository, the gate it finds is your own npm run test. It shows you the file rather than committing it.

  repo:      created
  identity:  local/widget, recorded for this clone (mirror mode)
  remote:    unchanged — you push to your existing remote, ADP observes
  mirror:    https://github.com/example-org/widget.git
  adp.yaml:  written — node (package.json)
             gate test: npm run test
What changes in your repository One new file, adp.yaml, which you review before committing
What does not change Your remote, your pull requests, your reviewers, your CI, your merge button. git clone keeps working throughout
What you give up while ingest is on Creating proposals and issues natively on that repository, and adp land — GitHub stays the merge authority

docs/companion-mode.md is the whole of it: the loop with what ADP records beside what you do, the two checks that appear on the pull request, and what it deliberately refuses to do.


For a real deployment, see docs/self-hosting.md; to run from source, Running it.


Why

Agents write most new code now, and the pattern is serial rather than a swarm: one capable agent iterates against CI until it believes the work is done, then submits. Fan-out is reserved for hard problems and fleet-wide remediation.

The belief is the problem. When the author decides when it's finished, the only oversight surface is a test suite the author can see — and often edit.

Git was built for a different job. Its conflicts halt automation, and it records what changed while discarding why and whether anyone checked. So when a change lands wrong, everything that follows — revert it, validate it, continue it with a different model or harness — needs a record that today exists only as a transcript in one vendor's format. Every harness privately reinvents the same primitives: checkpoint and rewind, session persistence, workspace orchestration. None is visible to the repository, and none can read another's.

ADP's bet is that the durable primitive is neither storage nor the change model, but binding context to verification evidence at merge time. Plenty of systems capture provenance; the point here is to make it enforceable — to turn the agent's belief into evidence someone else can check, revert, or build on.

git clone keeps working throughout.

The long version, with the field data and how every other entrant scores: Why ADP exists.


How it works

ADP serves two APIs over the same data. One imitates GitHub, so tools you already have keep working. The other exposes what GitHub has no equivalent for.

The compatibility plane is GitHub's surface: the git wire protocol, REST at /api/v3, and GraphQL at /api/graphql. An off-the-shelf agent or CI tool uses it with no knowledge that ADP exists. This is deliberately not an emulation layer bolted on top — the domain model is issues, proposals, reviews, and merges, so GitHub's shapes project onto it directly.

The native plane at /api/adp (and over MCP) exposes what has no GitHub analogue: the operation log, undo, evidence bundles, and workspaces.

The change record

Pushing a commit produces a signed changes row binding four things together:

Field What it captures
Intent the issue the work answers — filed as a typed intent, not free text
Diff the git commit itself; git remains the store
Evidence gate results for the commit, as DSSE-signed in-toto attestations
Provenance the pushing identity, plus harness / model / session where supplied

Schemas live in spec/schemas/ (change, evidence, provenance, operation); the REST surface is described in spec/openapi.yaml.

Admission control

Two mechanisms run at the point where code enters the system, both as real git hooks invoked by git receive-pack:

  • pre-receive runs push protection. A bundled regex-plus-entropy secret scanner rejects the push at the wire with a typed error naming the line and pattern. Because pre-receive runs while pushed objects are still in git's per-push object quarantine, the hook computes its diff locally and ships the text to the server, rather than shipping shas the server cannot yet resolve.

    That guards the diff, and a trajectory is a different object. It records what the agent read — file contents pulled into context, environment inspected, tool output returned. A .env the agent opened and correctly decided not to commit never appears in any diff, and would appear verbatim in a tool_call payload. So the same scanner runs at the trajectory ingest path too, before anything is hash-chained: a detected secret has its span replaced by a visible [redacted:<pattern>] marker, the event records redactions naming where and what fired, and the chain commits to the redacted form — so what verifies is what is stored, and a redaction can never be edited away afterwards. It is the same engine in a second place, not a second engine.

    A detector removes what it recognises, and the larger surface is everything else — source no pattern covers, customer data in a tool result, a prompt someone typed. So a trajectory stores the shape of a payload by default and not its content: objects, arrays, keys, numbers and booleans survive, and each string becomes [adp:str bytes=N]. The event carries payload_digest, sha256 of the canonical JSON of what was supplied, so a producer holding its own copy can still prove the record corresponds to it — "verified, payload not retained" as a real verification state rather than a hole. What answers what the agent did is untouched: the kind, the tool name, the outcome, the model, the token counts, the timings, the commit sha.

    Both settings live in adp.yaml, per repository:

    trajectory:
      on_secret: redact   # the default; `refuse` rejects the whole batch instead
      payloads: structure # the default; `full` stores payloads as supplied

    redact is the default deliberately. Both modes keep the secret out of the database — refuse additionally throws the trajectory away, and a lost trajectory teaches a user to turn recording off, which costs the record everything and costs the secret nothing.

    structure is the default on a different argument, and it is an asymmetry rather than a judgement about how much anyone should record: widening to full is available to a repo that has read what a trajectory holds, and unsending what already arrived is available to nobody. A full event carries no payload_digest — null is what says the payload is verbatim.

  • post-receive records a signed change per new commit, deduplicated by (repo, sha). A commit that carries an ADP-Intent trailer — the intent's id, or the issue number as #41 — is bound to that intent, and ADP-Session links it to a session; both are covered by the signature. The binding rides on git rather than on an API call, so it works from any harness, and a trailer naming something this repo does not have leaves the change unbound rather than failing the push.

Landing is governed by a three-level land policy: an instance floor (LAND_POLICY_FLOOR, admin-owned), unioned with the org's floor, unioned with the repo's own adp.yaml — each level can add requirements, never remove one. Both gates_green and one_approval are enforced identically on the REST and GraphQL merge paths, and a malformed adp.yaml fails closed. Merges are fast-forward only.

A fresh instance floors at gates_green alone. A merge is refused until the change has a green gate result, and that is the whole default — so one developer with one token can clone, push, gate and land without arranging anything. Turn approval on when there is somebody to give it, at whichever level owns the decision:

LAND_POLICY_FLOOR=gates_green,one_approval   # the whole instance, admin-owned
land:
  require: [one_approval]                     # one repo's adp.yaml, or an org's policy repo

Because the three levels union, a repo or an org can add one_approval to an instance that does not require it — but nothing below the instance can take it away again.

one_approval is author-independent: an approving review from the principal that authored the proposal does not satisfy it, so an agent cannot clear the requirement that exists to check it by approving its own work. Landing under it takes two principals, which is why it is not the default — a solo evaluator has one, and would be shown a refusal only somebody else could clear. Mint a second with tsx src/bootstrap.ts reviewer --org <owner>; an agent acting as an independent reviewer wants its own token for the same reason.

# adp.yaml, read off the base ref — as GitHub reads branch protection off the target branch
gates: [test, lint]
land:
  require: [gates_green, one_approval]

Evidence, and who executes it

The server receives and attests gate results; it never executes them. POST /api/v3/repos/{o}/{r}/gates signs and stores a result, GET .../commits/{sha}/gates lists them, and they project onto the compatibility plane as Commit.statusCheckRollup. This is the same division of labor as GitHub's Checks API: external systems report, the forge records and gates. No first-party scanner is built, by design — the bundled secret engine is the only in-tree detector.

Execution is a separate process. The gate runner in runner/ polls /api/adp/gate-jobs/claim, executes the job in an isolated container, and reports through the same signed path any external reporter uses. It is a pure HTTP client — no server import, no database credential, no signing key — and it belongs on its own host, because a mounted Docker socket is root on that host. What the isolation actually guarantees, what proves it, and what it explicitly does not claim are asserted in server/test/ and the runner's own suite.

Organizations

Repos live in orgs, and the org is the tenancy boundary. Repo access authorizes against the caller's org on every plane alike — REST, git wire, GraphQL and /api/adp — and the matrix that proves it is in server/test/e2e-org-isolation.test.ts.

An org carries four more things: its policy floor; a kill switch that refuses every land while set; quotas on repos, concurrent workspaces, concurrent gate jobs and stored bytes; and an audit-log export. That export is a projection of the operation log, not a second system. Org administration is itself audited: quota and policy-repo changes write operations, and the policy floor is a file in a repo, so changing it travels the same signed, reviewable path as code.


Using it

Point gh at a running server. Note GH_ENTERPRISE_TOKEN, not GH_TOKEN — that is what gh reads for any non-github.com host:

export GH_HOST=adp.example.com
export GH_ENTERPRISE_TOKEN=<token>

gh treats any unknown host as GitHub Enterprise Server and derives https://HOST/api/v3/, which is where ADP mounts. The same is true for Octokit and most CI libraries.

Clone and push with a token as the git password:

git clone https://x-access-token:<token>@adp.example.com/<owner>/<repo>.git

git

Smart HTTP is delegated to the real git http-backend CGI behind auth middleware, so clone, fetch, pull, push, ls-remote, and shallow, partial, and force-push variants behave exactly as git does. Delegating to git itself makes fidelity free. SSH is not served; sandboxed agents use HTTPS and a token.

gh

Functional means the command does real work against the domain model end to end. Partial means it is callable and answers honestly, but some of what GitHub would return is not backed by data here. The issue create/view and pr create/view/merge paths are driven by a real, unmodified gh binary against a live server on every CI run.

Command Status Notes
gh auth status Functional
gh repo view / clone Functional
gh repo create <owner>/<name> Functional resolves the owner through GET /api/v3/users/{owner}, then creates through the GraphQL createRepository mutation. The conformance suite runs it, so this row is enforced rather than asserted. The bare gh repo create <name> form is refused: ADP has no personal namespace, and every owner is an org
gh issue create / list / view / close Functional
gh issue comment Functional
gh pr create / list / view [--json] Functional
gh pr checkout Functional resolves the head ref, then a real git fetch
gh pr diff Functional REST Accept: …diff / …patch
gh pr review Functional
gh pr merge Functional subject to the land policy; refuses with a typed 422 naming each unmet requirement and the command that satisfies it
gh pr close / reopen Functional
gh pr comment Partial stored as an issue comment; PR conversation comments are not a separate subject
gh pr checks Functional each gate result is a StatusContext — name, verdict, and a link to its evidence bundle. Not a CheckRun: that shape implies a workflow run, which ADP deliberately does not have
gh pr ready Partial recorded as a no-op — there is no draft state; PRs are ready from creation
gh api <endpoint> Functional over the implemented surface see below
gh run / release / project / search Not supported returns a clear error

Unimplemented REST endpoints return 404 with a body naming the ADP equivalent. A broken call that explains itself costs an agent one turn; a hang or a 500 costs it the trajectory. Not served: search, Actions, releases, packages, orgs/teams, projects, deployments, branch protection, code scanning, Dependabot, notifications, gists. Branch protection, code scanning, and Dependabot are absent as API surfaces on purpose; their capabilities arrive natively through the land policy and push protection instead of endpoint emulation.

GraphQL loads GitHub's real published SDL (spec/graphql/github.graphql) unmodified into graphql-js and resolves only the fields ADP backs, including nine mutations. Everything else fails as a resolver error, never a schema validation error — which is what keeps a partial implementation from being worse than none, since gh's queries validate against the real schema.

Native plane

REST under /api/adp, and the same operations over MCP — 21 tools:

Capability REST MCP tool
Operation log GET .../operations, .../operations/{id} adp_op_log, adp_history_query
Undo POST .../operations/{id}/undo adp_undo
Evidence bundle GET .../evidence/{sha} adp_evidence_get
Workspaces GET/POST .../workspaces, DELETE .../workspaces/{id} adp_workspace_create, adp_workspace_destroy
Candidate sets GET/POST .../candidate-sets, POST .../candidate-sets/{id}/select adp_candidates_open, adp_candidates_select, adp_candidates_resolve
Sessions and checkpoints POST .../sessions, .../sessions/{id}/checkpoints, .../sessions/{id}/resume adp_session_start, adp_session_get, adp_checkpoint_create, adp_session_resume
Trajectories POST .../sessions/{id}/events, GET .../runs/{id}/trajectory adp_trajectory_append, adp_run_trajectory
Runs and evals GET .../runs/{id}/stats, GET .../runs/compare adp_run_stats, adp_runs_compare
Proposals POST /api/v3/.../pulls, .../pulls/{n}/reviews, PUT .../pulls/{n}/merge adp_proposal_open, adp_proposal_review, adp_proposal_merge
Intents GET /api/v3/.../issues/{n} adp_intent_get

The operation log is filterable by actor, verb, date range, and file path — path filtering resolves the commit behind an operation and asks git which paths it touched. Undo currently covers reverting a landed fast-forward merge, moving the base ref back by the same compare-and-swap the merge used; it refuses if the branch has moved since, rather than silently discarding what landed after. Other verbs return a 422 instead of a no-op that pretends to have worked.

A workspace is deliberately just a git branch with lifecycle metadata, not a new isolation mechanism. Destroying one deletes the ref and marks the row destroyed, so the log stays complete.

Candidate sets are the one primitive here with no GitHub analogue: N competing solutions to a single intent. A set is opened against an intent, proposals join it by passing candidate_set_id at creation, and one is eventually selected as the winner — the fan-out/compare/pick shape a fleet of agents actually produces, which a merge queue does not express.

The last two rows wrap /api/v3 rather than /api/adp, and that is deliberate: a proposal is the GitHub-shaped object, and an intent comes from an issue — nothing under /api/adp mints one. Giving either a second native spelling would be a fidelity problem, not a feature. Without these tools an agent driving the native plane had to break out to a raw curl to open a proposal or read its own task, which is one command against gh's one command and a hand-assembled HTTP request against the native plane's.

adp_proposal_merge returns a refused land as the typed policy result — each unmet requirement with what is missing and the command that satisfies it — rather than an error string. A refusal is the one response an agent is guaranteed to see on a well-configured instance, and an agent that cannot read it burns a turn guessing.

The MCP server is a thin wrapper over these same REST endpoints, so behavior is defined in one place rather than duplicated per protocol. Run it over stdio:

ADP_SERVER_URL=https://adp.example.com ADP_TOKEN=<token> npm run mcp

adp CLI

A thin command-line wrapper over the REST endpoints above, for scripting and CI steps that would otherwise be a raw curl. Published to npm as @deduva/adp, with no runtime dependencies:

npx @deduva/adp --help                                      # no install at all
npm install -g @deduva/adp                                  # or put `adp` on your PATH
adp login --server https://adp.example.com --token <token>  # writes ~/.adp/config.json

Until #235 the package was private: true and the only documented install was a source build, so ADP could not be obtained without cloning it — every front door this project widened opened onto a building with no road. The source build still works and is what you want when developing the CLI itself:

cd cli && npm ci && npm run build
npm link            # puts this checkout's `adp` on your PATH; undo with `npm unlink -g @deduva/adp`
Command Wraps
adp init [--mirror <url>] [--credential <token>] attaches ADP to the repository you are standing in: org, repo, mirror, and an adp.yaml detected from what the repo already says
adp login --server <url> --token <token> writes ~/.adp/config.json (or set ADP_SERVER_URL/ADP_TOKEN)
adp repo mirror <owner>/<repo> --remote-url <url> --secret <secret> --credential <credential> [--direction outbound|inbound|both] POST .../mirror
adp gate report --repo <owner>/<repo> --sha <sha> --name <name> --status <success|failure|pending> POST .../gates
adp pr list --repo <owner>/<repo> GET .../pulls
adp pr merge --repo <owner>/<repo> --number <n> [--method merge|squash|rebase] PUT .../pulls/{n}/merge
adp pr review --repo <owner>/<repo> --number <n> --state <approved|changes_requested|commented> POST .../pulls/{n}/reviews
adp watch --repo <owner>/<repo> [--pr <n>] the proposal, its gates, its runs, and whether it would land
adp undo <sha> --repo <owner>/<repo> finds the merge that produced the commit, and says which undo path it took
adp reimplement <sha> [--harness <name>] [--model <name>] [--launch] [--compare] the intent, the base, a second run related to the first, and the comparison
adp bakeoff --repo <owner>/<repo> --intent <uuid|#issue> --harness <a,b,c> [--launch] a candidate set, one labelled run per harness, and the comparison — --launch runs them, each in its own worktree
adp runner up --here starts a gate runner, or refuses and says why not here
adp connect <claude-code|codex|gemini-cli> [--model <name>] not a wrapper — see below
adp disconnect <harness> undoes exactly what connect wrote

adp connect

One command per harness, in the checkout you want connected:

adp connect claude-code

It mints a token carrying that harness's name, writes the harness's own MCP configuration in its own format at its own path, installs a prepare-commit-msg hook that adds the ADP-Intent trailer from the branch you are on, and wires up recording. Then it proves it worked by opening and closing a real session with the credential it just wrote — because a config written to the wrong path fails silently and looks exactly like success.

Everything it writes is inside the repository: .mcp.json for Claude Code, .codex/config.toml for Codex, .gemini/settings.json for Gemini CLI, and .adp/ for the recorder launcher. Those files hold a live token, so connect adds them to .git/info/exclude — a per-clone ignore, not a .gitignore entry, because telling every other contributor about one developer's harness is not its business. adp disconnect <harness> removes exactly what connect wrote, including that block.

Minting needs an admin scope. Without one connect still works and says which half you did not get: the harness reuses your own token, and the provenance on a signed change names no harness.

adp-recorder

The trajectory producer. It records what an agent actually did — every message, model call and tool call, in order and hash-chained — by reading a stream the harness is already producing, from a separate process. Nothing runs inside the agent's context window, so recording costs the agent nothing: 20 paired trials put it at −$0.0022 per trial, 95% CI [−$0.0073, +$0.0029].

Lives in recorder/, built like the CLI, and needs only repo:write — the scope a developer's own token already carries, so it runs as the developer rather than as infrastructure:

cd recorder && npm ci && npm run build && npm link   # puts `adp-recorder` on your PATH
export ADP_SERVER_URL=https://adp.example.com ADP_TOKEN=<token>

# follow a transcript the harness is already writing — it needs no flag and no
# knowledge that anything is watching
adp-recorder tail --repo <owner>/<repo> --file ~/.claude/projects/.../session.jsonl

# or run the harness through it
adp-recorder wrap --repo <owner>/<repo> --harness codex -- codex exec --json "fix the flake"

# and finish anything a previous recorder left undelivered
adp-recorder flush

The session lifecycle needs nothing typed. A session opens when the recorder attaches, bound to the intent HEAD's ADP-Intent trailer names; it checkpoints at boundaries — a commit, a handoff, a quiet stretch, the end — rather than on a timer, because a checkpoint is worth having when it is somewhere you would want to return to; and it ends as closed when the harness finished or suspended when it did not. Those last two are different facts, and an unclosed session is indistinguishable from an abandoned one.

A checkpoint names a commit, so ADP has to have that commit: one taken against work you have not pushed is deferred to the next boundary and the recorder says so. --continue picks up where this machine's last suspended session in the repository left off, across harnesses — the lineage chain that results was assembled by nobody:

adp-recorder wrap --repo <owner>/<repo> --harness codex --continue -- codex exec --json "carry on"

Which harnesses are covered. A reader translates one harness's private event names into ADP's fixed vocabulary. One ships for every harness adp connect offers, chosen for having a stable machine-readable event stream rather than for being the most popular:

--harness Reads Gets
claude-code (default) claude --output-format stream-json, or the session transcript it writes messages, model calls, tool calls with their outcomes, denied calls as rejected, per-session cost
codex codex exec --json messages and reasoning, shell and apply_patch and MCP calls with their outcomes, declined calls as rejected, per-turn tokens (Codex reports no cost)
gemini-cli gemini --output-format json, and the streaming form where the CLI offers one the answer, per-model token and latency totals, and tool calls with their outcomes where the stream reports them individually — where it reports only counts, the counts, said to be counts

That last cell is the interesting one, and it is why a third reader was worth having. Gemini's non-interactive run reports aggregate tool statistics rather than one event per call, and the reader records nine calls that arrived as the number nine as the number nine — a trajectory that claims detail it never had is worse than one that says what it has.

And what an uncovered harness still gets, which is most of it. Commit-level provenance, intent binding, gates, evidence bundles and land policy all ride on git and the commit trailer rather than on the harness, so they work from anything that can push. What is missing without a reader is turn-level detail — the trajectory itself, and the evals and checkpoints that hang off it.

Two ways to close that gap. Write a reader: it is a module exporting createReader() returning an object with read(line) and end(), documented in recorder/src/readers/index.ts, and it is loaded with --reader ./my-reader.js without this package changing. Or emit events directly to POST /api/adp/repos/{owner}/{repo}/sessions/{id}/events, which is what the recorder itself does.

The server stores harness as a string it never branches on — translation is the recorder's job, which is what keeps the protocol harness-neutral. An unknown --harness with no --reader is refused rather than defaulted, because recording one harness's stream through another's parser succeeds, looks like a recording, and is worthless.

Web UI

A read-only React SPA served at /ui/* by the same server. It shows issues and pull requests with their reviews, gate results, and diffs; the evidence view for a commit (signed provenance plus every DSSE gate attestation); and the operation log with filters. Its one interactive control is an Undo button on merge operations, calling the same endpoint the MCP tool and a direct API caller would.


Configuration

Variable Default Purpose
DATABASE_URL PostgreSQL connection string
GIT_ROOT directory holding bare repositories
SIGNING_KEY any secret string; the Ed25519 key is derived via SHA-256
PUBLIC_URL externally reachable base URL
PORT 3000 listen port
GIT_MAX_PACK_BYTES 500 MB bounds the git smart-HTTP request body only
LAND_POLICY_FLOOR gates_green instance floor; add one_approval for an instance with a second principal, empty string disables
OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET OpenID Connect login. Both client values must be present or the routes do not mount at all
OIDC_ALLOWED_DOMAINS empty empty means no auto-provisioning: a verified account with no existing link is refused, not welcomed
STORAGE_METER_INTERVAL_MS 600000 how often each org's storage is re-measured; also the overshoot an org can achieve past its quota

Auth is bearer tokens with repo:read / repo:write / admin scopes, enforced on every REST route, the GraphQL endpoint, and the git route. Reads are private by default. Token lookup is by an indexed sha256 key, with scrypt verification doing the actual authentication.


Running it

The server runs locally or under Docker Compose. Setup, bootstrapping the first token, and the three-tier test suite are documented in server/README.md.

cd server
npm install
npm run migrate
npm run dev

On a machine that has never seen this project, one command provisions it and one loop runs everything against a throwaway database that is destroyed afterwards:

bash scripts/dev/bootstrap.sh          # toolchain, Docker, dependencies
make up && make test-all && make down  # bring up, run, tear down, assert clean

make down asserts the machine is clean rather than assuming it — no leftover containers, volumes, server processes or temp directories. On Windows, tools/win/Run-CleanTest.ps1 runs the same loop inside a throwaway WSL distro and deletes it afterwards, so a full verification leaves nothing behind at all.

Self-hosting is documented in docs/self-hosting.md: a Helm chart (helm/adp) and the Docker Compose path in deploy/, what the two decisions are that cannot be defaulted for you, and why the gate runner needs a node of its own.

CI runs typecheck, build, migrations against a fresh Postgres, the full unit/integration/e2e suite — including a real clone → push → propose → review → merge cycle — and the gh conformance gate, on every pull request. A separate clean-room workflow provisions a bare container from scratch and runs the same loop, so the "brand new machine" path stays verified rather than assumed.


Documents

Document What it is
docs/companion-mode.md ADP underneath GitHub: what stays on github.com, what ADP records without a command being run, the two checks that appear on the pull request, and what it deliberately refuses to do.
docs/self-hosting.md Running your own instance: the Helm chart, the Compose path, what the chart refuses to guess and why, and where the sharp edges are.
docs/api-compatibility.md What the contract version promises, and what a bump means for a generated client.
docs/server-stack-tutorial.md The server stack explained piece by piece, no prior familiarity assumed.
docs/observability.md What is measured, what pages, and what to do when it does.
docs/ecosystem.md Who depends on ADP and how — the dependency graph, and what a change here requires elsewhere. Read this before changing the wire contract.
CHANGELOG.md What shipped, per released version.
PLAN.md The backlog: what is left, in what order, and the open decisions.

Contributing to ADP itself needs two more. AGENTS.md has the branch and review conventions, the invariants that look wrong until you know why, and the commands that actually run. docs/test-environment-automation.md covers how the test environment is brought up and torn down.


License

MIT — code, spec, conformance suites, and prose alike. Nothing in this repository needs a second look from legal before you run it, fork it, or build on it.

About

A GitHub-compatible forge for AI coding agents that binds every change to signed intent, provenance, and verification evidence.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages