Document pull requests and stacked pull requests

8a671b61d0a6 · Devin AI · · parent 65bb3edbbe49

Document pull requests and stacked pull requests

Five new /docs pages cover pull requests, stacks, stack actions,
merging, and the stacks API, catalogued under a new Pull requests
section. rest-api.md gains the pull request and stack routes and no
longer claims pull requests are unimplemented, and issues.md notes
that pull requests share the issue number sequence.

Co-Authored-By: Christopher David <chris@openagents.com>
Co-Authored-By
Christopher David <chris@openagents.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified lib/openagents_web/docs_catalog.ex
  • modified priv/docs/issues.md
  • added priv/docs/merging-stacks.md
  • added priv/docs/pull-requests.md
  • modified priv/docs/rest-api.md
  • added priv/docs/stack-actions.md
  • added priv/docs/stacked-pull-requests.md
  • added priv/docs/stacks-api.md

Diff

8 files changed, +499 -2

lib/openagents_web/docs_catalog.ex modified +35

@@ -118,6 +118,35 @@ defmodule OpenAgentsWeb.DocsCatalog do

118 118
        }
119 119
      ]
120 120
    },
121
    %{
122
      title: "Pull requests",
123
      items: [
124
        %{
125
          slug: "pull-requests",
126
          title: "Pull requests",
127
          icon: "pull-request-open",
128
          route: "/:owner/:repo/pulls"
129
        },
130
        %{
131
          slug: "stacked-pull-requests",
132
          title: "Stacked pull requests",
133
          icon: "stack",
134
          route: "/:owner/:repo/pulls/:number"
135
        },
136
        %{
137
          slug: "stack-actions",
138
          title: "Rebase and restructure a stack",
139
          icon: "reload",
140
          route: "/:owner/:repo/pulls/:number"
141
        },
142
        %{
143
          slug: "merging-stacks",
144
          title: "Merging stacks",
145
          icon: "check-circle",
146
          route: "/api/v3/repos/:owner/:repo/stacks"
147
        }
148
      ]
149
    },
121 150
    %{
122 151
      title: "Projects",
123 152
      items: [

@@ -170,6 +199,12 @@ defmodule OpenAgentsWeb.DocsCatalog do

170 199
          icon: "square-code",
171 200
          route: "/api/v3/repos/:owner/:repo/issues"
172 201
        },
202
        %{
203
          slug: "stacks-api",
204
          title: "Stacks API",
205
          icon: "square-code",
206
          route: "/api/v3/repos/:owner/:repo/stacks"
207
        },
173 208
        %{slug: "status-api", title: "Status API", icon: "info", route: "/api/status"}
174 209
      ]
175 210
    }
priv/docs/issues.md modified +4

@@ -15,6 +15,10 @@ A title, a description, a state, and the people and labels attached to it. Each

15 15
issue carries a number that is unique within its repository and never reused,
16 16
so a reference to an issue stays valid after it is closed.
17 17
18
[Pull requests](/docs/pull-requests) share the same number sequence: a pull
19
request is an issue paired with a branch comparison, so issue and pull
20
request numbers never collide within a repository.
21
18 22
## Comments
19 23
20 24
Comments are ordered and attributed. Editing one is limited to its author,
priv/docs/merging-stacks.md added +85

@@ -0,0 +1,85 @@

1
# Merging stacks
2
3
A stack merges bottom-first. You can land the whole stack or any contiguous
4
prefix of it — layers 1 and 2 while layer 3 is still in review — but never a layer
5
whose foundations have not landed.
6
7
Merging requires write access, and merge policy is evaluated against the
8
trunk for every layer. A layer's direct base being an intermediate branch
9
never relaxes what the trunk requires.
10
11
## Merge a prefix in one operation
12
13
`POST /api/v3/repos/:owner/:repo/stacks/:stack_number/merge` lands a
14
contiguous prefix as one operation:
15
16
```sh
17
curl -X POST \
18
  -H "Authorization: Bearer $OPENAGENTS_TOKEN" \
19
  -H "Content-Type: application/json" \
20
  -H "Idempotency-Key: $(uuidgen)" \
21
  -d '{"pull_requests": [117, 118], "merge_method": "merge"}' \
22
  https://openagents.com/api/v3/repos/acme/api/stacks/1/merge
23
```
24
25
- `pull_requests` must be a contiguous prefix from the bottom of the stack.
26
  A gap or a middle-only selection is refused.
27
- `merge_method` is `merge`, `squash`, or `rebase`.
28
- `expected_stack_version` and expected head OIDs are optional guards; when
29
  supplied, a stack that moved since you read it fails the merge instead of
30
  landing something you did not review.
31
32
The request returns `202 Accepted` with a durable operation. Poll `GET
33
.../stacks/:stack_number/operations/:operation_id` until it succeeds or
34
fails. The trunk moves once for the whole prefix. Merged pull requests close as
35
merged, their entries leave the stack, and the layers above become the new
36
bottom.
37
38
## Merge one layer asynchronously
39
40
`PUT /api/v3/repos/:owner/:repo/pulls/:pull_number/merge-async` submits a
41
merge against one stacked pull request — it lands the contiguous prefix from
42
the bottom of the stack through that layer — and returns `202 Accepted`
43
immediately with an `operation_id` and a poll URL:
44
45
```sh
46
curl -X PUT \
47
  -H "Authorization: Bearer $OPENAGENTS_TOKEN" \
48
  -H "Content-Type: application/json" \
49
  -H "Idempotency-Key: $(uuidgen)" \
50
  -d '{"merge_method": "squash"}' \
51
  https://openagents.com/api/v3/repos/acme/api/pulls/117/merge-async
52
```
53
54
Poll `GET .../pulls/:pull_number/merge-async/:operation_id` for the outcome.
55
`merge_status` is `pending` while the operation runs, then `merged` or
56
`failed`. `merge_method` defaults to `merge`. Repeating the request with the
57
same `Idempotency-Key` and body replays the same operation instead of
58
starting a second one; a different active operation on the stack returns
59
`409 Conflict` with that operation's id.
60
61
The submission is validated when it executes, not when it is accepted:
62
branch heads, stack membership, and merge policy are all checked at execution
63
time. An unstacked pull request cannot merge through this surface.
64
65
## Automatic restack after a partial merge
66
67
Landing a prefix rewrites the foundation under the remaining layers, so a
68
partial merge restacks them automatically: the remaining branches are rebased
69
onto the new trunk tip in the same coordinated fashion as
70
[Rebase the stack](/docs/stack-actions), and the stack's version advances.
71
The layers left behind stay reviewable without a manual cleanup step, and a
72
restack that would conflict fails the merge during planning — it never lands
73
the prefix and strands the rest.
74
75
## What atomic means here
76
77
Within one merge operation, the trunk either gains the whole requested prefix
78
or none of it — a conflict or policy failure partway lands nothing. It does
79
not mean the trunk pauses for you: another merge can land between your read
80
and your merge, which is what the `expected_stack_version` guard is for.
81
82
## Next steps
83
84
- [Stacked pull requests](/docs/stacked-pull-requests)
85
- [Stacks API](/docs/stacks-api)
priv/docs/pull-requests.md added +67

@@ -0,0 +1,67 @@

1
# Pull requests
2
3
A pull request proposes merging one branch into another. Browse a
4
repository's pull requests at `/:owner/:repo/pulls`, and open one to review
5
its changes at `/:owner/:repo/pulls/:number`.
6
7
## What a pull request holds
8
9
A pull request pairs an issue with a branch comparison. The issue carries the
10
title, description, comments, state, and a number that is unique within the
11
repository and shared with plain issues. The comparison names a head branch,
12
a base branch, and the exact commits each pointed at when the comparison was
13
last observed, so the review diff is reproducible rather than whatever the
14
branches happen to say later.
15
16
A new pull request starts as a draft. Publish it by clearing the draft flag
17
when it is ready for review.
18
19
## The review diff
20
21
The pull request page shows the changes the head branch adds over the base
22
branch, as a unified diff with per-file collapse. When the pull request
23
belongs to a [stack](/docs/stacked-pull-requests), the page adds a stack map
24
and a layer-aware diff so each layer reviews only its own changes.
25
26
## Create one through the API
27
28
Pull requests are created through the REST API. `POST
29
/api/v3/repos/:owner/:repo/pulls` takes:
30
31
- `title` — required.
32
- `head` — required; the branch the work lives on.
33
- `head_repository` — required; the `owner/name` of the repository holding
34
  `head`. You need write access to it.
35
- `base` — optional; defaults to the repository's default branch.
36
- `body` — optional description.
37
- `draft` — optional; defaults to `true`.
38
39
```sh
40
curl -X POST \
41
  -H "Authorization: Bearer $OPENAGENTS_TOKEN" \
42
  -H "Content-Type: application/json" \
43
  -d '{"title": "Add rate limiting", "head": "rate-limit",
44
       "head_repository": "acme/api", "base": "main"}' \
45
  https://openagents.com/api/v3/repos/acme/api/pulls
46
```
47
48
`PATCH /api/v3/repos/:owner/:repo/pulls/:pull_number` updates `title`,
49
`body`, `state`, `draft`, and `base`. While a pull request is an active stack
50
member its base belongs to the stack, so a direct base edit is refused —
51
restructure the stack instead.
52
53
Pull requests can be switched off per repository. When they are off, creation
54
returns `409 Conflict`.
55
56
## Merging
57
58
Merging is currently a stack operation: a pull request merges through the
59
stack it belongs to, and a stack can be as small as one pull request. See
60
[Merging stacks](/docs/merging-stacks). A standalone merge button for
61
unstacked pull requests is not implemented yet.
62
63
## Next steps
64
65
- [Stacked pull requests](/docs/stacked-pull-requests)
66
- [Rebase and restructure a stack](/docs/stack-actions)
67
- [Stacks API](/docs/stacks-api)
priv/docs/rest-api.md modified +27 -2

@@ -62,6 +62,29 @@ DELETE /api/v3/repos/:owner/:repo/issues/comments/:id

62 62
List responses use named envelopes. For example, the issue list returns an
63 63
object with an `issues` array.
64 64
65
## Pull requests and stacks
66
67
```text
68
GET    /api/v3/repos/:owner/:repo/pulls
69
POST   /api/v3/repos/:owner/:repo/pulls
70
GET    /api/v3/repos/:owner/:repo/pulls/:pull_number
71
PATCH  /api/v3/repos/:owner/:repo/pulls/:pull_number
72
73
GET    /api/v3/repos/:owner/:repo/stacks
74
POST   /api/v3/repos/:owner/:repo/stacks
75
GET    /api/v3/repos/:owner/:repo/stacks/:stack_number
76
POST   /api/v3/repos/:owner/:repo/stacks/:stack_number/append
77
POST   /api/v3/repos/:owner/:repo/stacks/:stack_number/rebase
78
POST   /api/v3/repos/:owner/:repo/stacks/:stack_number/merge
79
POST   /api/v3/repos/:owner/:repo/stacks/:stack_number/unstack
80
POST   /api/v3/repos/:owner/:repo/stacks/:stack_number/dissolve
81
PUT    /api/v3/repos/:owner/:repo/pulls/:pull_number/merge-async
82
```
83
84
See [Pull requests](/docs/pull-requests) for the pull request endpoints and
85
the [Stacks API](/docs/stacks-api) for stacks, durable operations,
86
idempotency, and optimistic concurrency.
87
65 88
## Issue prerequisites
66 89
67 90
An issue can wait on other issues in the same repository.

@@ -280,5 +303,7 @@ editable. See [Projects](/docs/projects).

280 303
281 304
## Know what is not implemented
282 305
283
Pull requests, reviews, webhooks, releases, SSH Git transport, and Git LFS
284
object storage are outside the current subset.
306
Pull request reviews, webhooks, releases, SSH Git transport, and Git LFS
307
object storage are outside the current subset. Pull requests and stacked pull
308
requests are implemented — see [Pull requests](/docs/pull-requests) and the
309
[Stacks API](/docs/stacks-api).
priv/docs/stack-actions.md added +82

@@ -0,0 +1,82 @@

1
# Rebase and restructure a stack
2
3
A stack stays useful only while its branches stay coherent. These actions
4
keep them that way, and every one of them runs on the server as a durable
5
operation rather than as a sequence of client-side pushes that can be
6
interrupted halfway.
7
8
All of them require write access to the repository.
9
10
## Rebase the stack
11
12
When the trunk moves, or a lower layer is rewritten, the layers above it go
13
stale. Click **Rebase the stack** on any stacked pull request page — it also
14
appears in the stale-boundary notice — or call `POST
15
/api/v3/repos/:owner/:repo/stacks/:stack_number/rebase`.
16
17
The rebase works bottom-to-top on the server:
18
19
1. It verifies every branch still points where the stack last observed it. A
20
   branch that moved since fails the operation instead of being silently
21
   overwritten.
22
2. It replays each layer's own commits onto the new base, keeping author
23
   identity. Server rebases are unsigned.
24
3. It moves every branch in one atomic batch. Either the whole stack moves or
25
   none of it does — a concurrent push to any stack branch makes the batch
26
   fail and preserves that push.
27
28
The pull request page shows the operation's progress — queued, running,
29
finished, or failed — with a **Check progress** button. Only one operation
30
can run per stack at a time; a second request while one is active returns the
31
active operation instead of starting another.
32
33
### Conflicts
34
35
A rebase that hits a conflict pauses rather than failing. The operation
36
reports which layer conflicted, and the API offers two ways out:
37
38
- `POST .../stacks/:stack_number/operations/:operation_id/continue` — resolve
39
  the conflict yourself, push the resolution commit, and pass its OID as
40
  `resolution_oid`. The operation verifies it and finishes the remaining
41
  layers.
42
- `POST .../stacks/:stack_number/operations/:operation_id/abort` — give up.
43
  No branch moves; the stack is exactly as it was.
44
45
Both re-verify branch heads before acting, so a resolution computed against
46
branches that have since moved is refused rather than applied.
47
48
## Remove the top layer
49
50
**Remove from stack** appears on the stack's top layer only. It detaches that
51
pull request from the stack — the pull request itself stays open and stops
52
being a layer. The equivalent API is `POST
53
/api/v3/repos/:owner/:repo/stacks/:stack_number/unstack` with
54
`{"pull_request": <number>}`.
55
56
Only the top layer can leave, because removing a middle layer would orphan
57
the branches above it. To take a middle layer out, remove layers from the top
58
down to it, or dissolve the stack.
59
60
## Grow the stack
61
62
`POST /api/v3/repos/:owner/:repo/stacks/:stack_number/append` adds one open
63
pull request on top. Its base branch must be the current top layer's head
64
branch.
65
66
## Dissolve the stack
67
68
`POST /api/v3/repos/:owner/:repo/stacks/:stack_number/dissolve` closes the
69
stack as an object while leaving every pull request open and every branch
70
where it is. Use it when the layers should continue as independent pull
71
requests.
72
73
A stack with an operation in flight refuses to unstack or dissolve until the
74
operation finishes.
75
76
These are the restructure operations available today. Reordering layers in
77
place is not supported — recreate the stack in the order you want instead.
78
79
## Next steps
80
81
- [Merging stacks](/docs/merging-stacks)
82
- [Stacks API](/docs/stacks-api)
priv/docs/stacked-pull-requests.md added +94

@@ -0,0 +1,94 @@

1
# Stacked pull requests
2
3
A stack is an ordered series of dependent pull requests that merge
4
bottom-first into one trunk branch. Each layer's branch is based on the layer
5
below it, so a large change ships as several small reviews without losing the
6
dependency structure between them.
7
8
## Why stack
9
10
One big pull request is hard to review, and splitting it into independent
11
pull requests loses the ordering. A stack keeps both properties:
12
13
- Each layer is a small, focused review that shows only its own changes.
14
- The order is explicit. Layer 3 cannot land before layer 2, and the forge
15
  enforces that rather than trusting reviewers to remember it.
16
- Branch maintenance cascades. When the trunk or a lower layer moves, one
17
  server-side rebase moves every branch above it at once.
18
19
## The shape of a stack
20
21
A stack is a first-class object, not a naming convention. It holds:
22
23
- A **trunk** — the branch the whole stack eventually merges into, usually
24
  the default branch.
25
- **Ordered layers** — positions count up from 1 nearest the trunk. Each
26
  layer is one open pull request whose base branch is the head branch of the
27
  layer below (the bottom layer's base is the trunk).
28
- A **number** — unique within the repository, like an issue number.
29
- A **version** — increments on every structural change, so concurrent
30
  writers can detect that the stack moved underneath them.
31
- A **health** — `healthy`, or stale when a lower branch was rewritten and
32
  the layers above it need a rebase before their diffs are trustworthy.
33
34
Every layer keeps two bases. The *direct base* is the branch directly below,
35
and it defines what the layer's own review shows. The *effective base* is the
36
trunk, and it is what merge policy evaluates against — an intermediate branch
37
is never a policy target, so stacking cannot be used to slip changes past the
38
rules that protect the trunk.
39
40
## The stack map
41
42
Every pull request in a stack shows a stack map: the ordered rail of layers
43
with the newest on top, a state glyph per layer, the current layer
44
highlighted, and a trunk row that links to the trunk's file tree. Click any
45
other layer to jump to its pull request.
46
47
Below the map, a readiness line summarizes the whole stack — how many layers
48
are ready to merge bottom-first, how many are still drafts, or that the stack
49
needs a rebase first.
50
51
Members with write access also see the stack actions here. See
52
[Rebase and restructure a stack](/docs/stack-actions).
53
54
## Layer and cumulative review
55
56
A stacked pull request's diff has two views:
57
58
- **This layer** — the changes this layer adds over the layer below it. This
59
  is the default review view, computed from the boundary the stack recorded,
60
  so it shows only this layer's work even while other layers move.
61
- **Cumulative** — everything the stack contains through this layer,
62
  measured from the current trunk tip. Because the comparison starts at the
63
  *current* trunk, this view also picks up drift when the trunk has moved
64
  since the stack was created; it answers "what would the trunk gain", not
65
  "what did this author write".
66
67
When a lower layer is rewritten, the layer view above it becomes untrustworthy.
68
The page says so explicitly and offers the rebase action instead of showing a
69
diff that silently mixes in another layer's changes.
70
71
## Create a stack
72
73
Open the pull requests so their branches form a chain — each head is the next
74
base — then create the stack through the API:
75
76
```sh
77
curl -X POST \
78
  -H "Authorization: Bearer $OPENAGENTS_TOKEN" \
79
  -H "Content-Type: application/json" \
80
  -H "Idempotency-Key: $(uuidgen)" \
81
  -d '{"trunk_ref": "main", "pull_requests": [117, 118, 119]}' \
82
  https://openagents.com/api/v3/repos/acme/api/stacks
83
```
84
85
The order in `pull_requests` runs bottom-first. Creation validates the whole
86
structure — every pull request open, in this repository, unduplicated, and
87
chained base-to-head — and refuses anything else. A stack holds at most 100
88
layers. See [Stacks API](/docs/stacks-api) for the full surface.
89
90
## Next steps
91
92
- [Rebase and restructure a stack](/docs/stack-actions)
93
- [Merging stacks](/docs/merging-stacks)
94
- [Stacks API](/docs/stacks-api)
priv/docs/stacks-api.md added +105

@@ -0,0 +1,105 @@

1
# Stacks API
2
3
The stack surface lives under `/api/v3` beside the rest of the
4
[REST API](/docs/rest-api). Reads are public on a public repository; writes
5
require an `oa_pat_` bearer token with `forge:write` scope and an
6
`Idempotency-Key` header.
7
8
## Routes
9
10
```text
11
GET  /api/v3/repos/:owner/:repo/pulls
12
GET  /api/v3/repos/:owner/:repo/pulls/:pull_number
13
POST /api/v3/repos/:owner/:repo/pulls
14
PATCH /api/v3/repos/:owner/:repo/pulls/:pull_number
15
16
GET  /api/v3/repos/:owner/:repo/stacks
17
GET  /api/v3/repos/:owner/:repo/stacks/:stack_number
18
POST /api/v3/repos/:owner/:repo/stacks
19
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/append
20
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/rebase
21
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/merge
22
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/unstack
23
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/dissolve
24
25
GET  /api/v3/repos/:owner/:repo/stacks/:stack_number/operations/:operation_id
26
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/operations/:operation_id/continue
27
POST /api/v3/repos/:owner/:repo/stacks/:stack_number/operations/:operation_id/abort
28
29
PUT  /api/v3/repos/:owner/:repo/pulls/:pull_number/merge-async
30
GET  /api/v3/repos/:owner/:repo/pulls/:pull_number/merge-async/:operation_id
31
```
32
33
## The stack payload
34
35
A stack read returns the stack's `number`, `trunk_ref`, `state`, `health`,
36
`version`, and its active `entries` in position order. Each entry carries its
37
`position` and the pull request's `number`, `head` (`ref` and `sha`), and
38
`base` (`ref` and `sha`), so one read gives you the complete chain and the
39
exact commits the stack has observed.
40
41
```sh
42
curl https://openagents.com/api/v3/repos/acme/api/stacks/1
43
```
44
45
## Idempotency
46
47
Every write takes an `Idempotency-Key`. Repeating a request with the same key
48
and the same body returns the original result instead of acting twice; the
49
same key with a different body is refused with `409 Conflict`. Use a fresh
50
UUID per intended action and retry with the same one on network failure.
51
52
## Optimistic concurrency
53
54
A stack's `version` increments on every structural change. Writes accept an
55
optional `expected_stack_version`, and the mutating operations verify branch
56
heads against what the stack last observed before moving anything. When
57
either check fails you get a conflict rather than a mutation built on a stack
58
you have not seen — read again, re-decide, and resubmit.
59
60
## Operations
61
62
Rebase and merge run as durable operations. The submitting request returns
63
`202 Accepted` with the operation, and `GET
64
.../operations/:operation_id` reports its `state`: `pending`, `running`,
65
`waiting_for_conflict_resolution`, `succeeded`, `partially_succeeded`,
66
`failed`, or `cancelled`. One operation runs per stack at a time; submitting
67
while one is active returns `409 Conflict` carrying the active
68
`operation_id`. A paused rebase resumes through
69
[`continue` or `abort`](/docs/stack-actions).
70
71
## Worked example
72
73
Create three chained pull requests, stack them, and merge the bottom two:
74
75
```sh
76
repo=https://openagents.com/api/v3/repos/acme/api
77
auth="Authorization: Bearer $OPENAGENTS_TOKEN"
78
79
# One pull request per layer: base of each is the head of the one below.
80
curl -X POST -H "$auth" -H "Content-Type: application/json" \
81
  -d '{"title": "Layer 1", "head": "layer-1",
82
       "head_repository": "acme/api", "base": "main"}' $repo/pulls
83
curl -X POST -H "$auth" -H "Content-Type: application/json" \
84
  -d '{"title": "Layer 2", "head": "layer-2",
85
       "head_repository": "acme/api", "base": "layer-1"}' $repo/pulls
86
curl -X POST -H "$auth" -H "Content-Type: application/json" \
87
  -d '{"title": "Layer 3", "head": "layer-3",
88
       "head_repository": "acme/api", "base": "layer-2"}' $repo/pulls
89
90
# Stack them bottom-first.
91
curl -X POST -H "$auth" -H "Content-Type: application/json" \
92
  -H "Idempotency-Key: $(uuidgen)" \
93
  -d '{"trunk_ref": "main", "pull_requests": [1, 2, 3]}' $repo/stacks
94
95
# Land layers 1 and 2; layer 3 restacks automatically.
96
curl -X POST -H "$auth" -H "Content-Type: application/json" \
97
  -H "Idempotency-Key: $(uuidgen)" \
98
  -d '{"pull_requests": [1, 2], "merge_method": "merge"}' $repo/stacks/1/merge
99
```
100
101
## Next steps
102
103
- [Stacked pull requests](/docs/stacked-pull-requests)
104
- [Merging stacks](/docs/merging-stacks)
105
- [REST API](/docs/rest-api)

This page updates live while a promote is in flight · changelog