Delivery: pull requests, review, merge, deploy, UAT¶
Pull requests¶
When the target repository is GitHub-hosted, the implement, orchestrate,
review and verify stages commit the feature branch, push it and open or
update a pull request against the default branch. review posts the code
review as a PR comment; verify adds a comment with the verification status.
Parallel workstreams each run in an isolated git worktree on their own branch
and get their own PR. A workstream whose ### Dependencies names another
workstream is branched from that workstream's branch and its PR targets it — a
stacked PR — and workstreams run in dependency order.
Every commit message and PR title the pipeline writes follows
Conventional Commits: type(scope): subject, lowercase subject, no trailing
period, header ≤ 72 characters. implement uses feat, verify uses test,
orchestrate uses chore; the scope is the feature branch or workstream.
While a feature is implementing or releasing, its board card and project page
link to each open pull request on GitHub (PR #12, or repo#12 when the
feature spans repositories).
Identity: the app bot opens pull requests¶
With a GitHub App set up through Organization → Integrations, every push,
pull request, review comment and merge an agent performs uses a short-lived
installation token, so it is attributed to <app>[bot] rather than to the
person who connected GitHub. Agent shells receive the same identity through
GH_TOKEN and git's http.extraheader, so gh and git push inside a run
behave the same way. Reads (repository catalog, cloning) still use the user.
The app asks for repository contents, pull requests, issues and workflows
(read and write), plus metadata, checks and actions (read). Workflows matters:
GitHub refuses any push that touches .github/workflows from an app without
it, so a run asked to wire its tests into CI cannot deliver the change at all.
An app created before that permission was requested keeps working for
everything else; Organization → Integrations says which permissions are
missing and links to the app's page on GitHub, where adding them raises a
request to accept on the installation.
Because the bot is a separate identity, a maintainer can approve the agent's
pull requests, and branch protection on main (a pull request with one
approval and green checks, stale reviews dismissed, no force pushes,
administrators not included) means the agent can never merge on its own
while the owner keeps direct access. The deliver stage asks before merging
and then merges only once the protection rules are satisfied.
What is committed to implementation repositories¶
Planning stays in the governing workspace; each implementation repository
gets a repo-local change, committed with the code on the same branch and
pull request, in the same specs/ directory Spec Kit uses:
<repo>/specs/<NNN-intent>/
change.yaml schema, created, initiative, repository, links to sibling changes
tasks.md the tasks this repository owns (its workstreams), as a checklist
spec.md the delta spec as seen from this repository
The governing workspace's feature directory is the initiative: alongside
spec.md, plan.md, tasks.md and the rest it carries initiative.yaml and
links.yaml naming every implementation repository and its change. Links use
stable project identifiers (github.com/org/repo, or local/<dir> for
unhosted checkouts), never filesystem paths, and are informational: a missing
sibling never fails anything. Sub-agents tick tasks.md as work lands and
leave change.yaml and spec.md alone; PR bodies name the change and the
initiative. The pattern follows OpenSpec's workspace architecture design
decisions (initiative-first planning with linked repo-local changes).
The intent's documents in implementation repositories¶
An implementation repository carries only its repo-local change, never the
whole intent. At every code stage (implement, orchestrate, review, verify),
each implementation repository that is on the feature branch and has work there
gets specs/<intent>/, under the same numbered name as in the governing
workspace (e.g. specs/003-search/), committed with that stage's changes:
| File | Contents |
|---|---|
change.yaml |
schema, created, initiative, project, repository, links to sibling changes |
tasks.md |
the tasks this repository owns: its workstreams from parallel-workstreams.md, else the governing tasks that name it, else all of them, each ticked as it is in the governing tasks.md |
spec.md |
the delta spec as seen from this repository: its scope, then the initiative's specification |
The plan, test plan, research, data model, contracts, code review, verification and delivery reports stay in the governing workspace and in Spaces (the intent record). Documents an earlier version mirrored into that directory are pruned, only those that are exact copies of the governing ones (anything written in the repository stays), and nothing is removed through a symbolic link. A project without a separate governing workspace keeps the real intent in that directory, so no repo-local change is written over it.
Those repositories are then pushed and get their own pull request
(feat(<intent>): <spec title>), which links to the governing workspace's pull
request and lists the repo-local change it carries. This also happens when the
governing workspace has no GitHub repository. Review and verification results
are posted to every pull request on the feature branch. Work on the branch is
measured against each repository's real default branch (as GitHub reports it),
so develop or trunk repositories are included. The run log shows
[specs] <repo>: specs/003-search (change.yaml, tasks.md, spec.md) updated for
implement and [pr] <repo>: opened pull request #N ….
Code review loop¶
The review stage refreshes CI and PR state from GitHub, reviews the diff
against the spec, plan and test plan, runs lint and tests itself, and writes
code-review.md:
Code Review Status: CHANGES_REQUESTED
## Summary
## Findings
- [BLOCKER|MAJOR|MINOR|NIT] path:line — what is wrong — what to do
## Tests & checks
## Spec coverage
CHANGES_REQUESTED loops back to implement, which must address every
blocker and major finding (and any failing CI) before continuing; APPROVED
proceeds to verify.
Deliver stage¶
tasks ends with a per-repository ## Delivery group in dependency order.
Before the deliver stage runs, delivery-status.md is refreshed from
GitHub: every PR the feature opened, its review, CI, merge and deployment
state, ordered by stack, with a suggested next action.
The agent then:
- fixes what blocks a PR itself — rebase, CI, review comments, missing PRs;
- asks for approval and pauses before merging or deploying;
- confirms deployments reached their environment;
- runs the UAT scenarios from
test-plan.mdagainst the deployed environment (or the full suite on the merged base and says UAT still needs an environment); - writes
delivery-report.mdwithDelivery Status: MERGED | PARTIAL | BLOCKED.
Templates loop deliver on delivery_status != 'merged' after the human gate,
so each approval refreshes the status and re-checks until everything is merged.