feat(forge): mergePullRequest + review-state parity across forges (MF-8) #313
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!313
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/299-mf8-merge-review-parity"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
ForgePort.mergePullRequest(repo, prNumber, opts)and implements it on all three adapters so domain code can squash-merge a PR without caring which forge hosts it.ForgejoAdaptercallsites inwebhook-ci.ts+review-loop.tsontocreateForgeAdapterForRepo(...)— a GitHub- or GitLab-hosted PR now picks up the correct adapter transparently. No-op for forgejo-bound repos.Design notes
Per-adapter merge mapping:
POST /pulls/{n}/mergewithDo: "squash", title/message forwarded asMergeTitleField/MergeMessageField.PUT /pulls/{n}/mergewithmerge_method: "squash", title/message ascommit_title/commit_message.PUT /merge_requests/{iid}/mergewithsquash: true, title/message assquash_commit_message/merge_commit_message.Squash-only contract: per MF-8 spec "Out of scope" — rebase-merge, merge-commit and merge queues are not supported.
opts.squashis accepted for port symmetry but every adapter always sends a squash; documenting it in the port doc so the shape reads naturally at callsites without implying non-squash strategies are wired.Return contract:
trueon any 2xx,falseon non-2xx (405 "not mergeable", 409 "SHA out of date", auth failure, transport error). Callers that need to distinguish cases can probegetPullRequestformerged/mergeable.Review-state parity: audited the consuming code in
review-loop.ts+webhook-ci.ts+webhook-handlers.ts. The domain already speaks theReviewStateunion ("approved","changes_requested","comment", …) — only the adapters'toReviewStatemappers touch the rawAPPROVED/CHANGES_REQUESTED/COMMENTEDstrings, which is correct. Remaining"APPROVED"/"REQUEST_CHANGES"string literals in the review flow are log/comment prose, not state checks.Force-merge path: confirmed — the round-cap counter and pill-red logic are forge-agnostic (
countChangeRequestRounds+guardAuthorDispatch), and the terminaldispatchMerge(…, { force: true })call still hands off to the boss agent which performs the squash through MCP. No direct host-side merge call exists on the force-merge path today;mergePullRequestis now available for any future automation that wants to bypass the agent-dispatch indirection.Test plan
bun test apps/server/src/infrastructure/forge/— 182 pass (adds 12 newmergePullRequestassertions across Forgejo/GitHub/GitLab)bun test apps/server/src/domain/workflow/review-loop.test.ts apps/server/src/domain/workflow/force-merge.integration.test.ts apps/server/src/http/webhook-ci.test.ts apps/server/src/http/webhook-handlers.test.ts apps/server/src/http/webhook.test.ts— 137 passbun x turbo run typecheckclean across all 4 packagesbun x biome checkcleanForgejoAdapterOut of scope (flagged)
submitReviewport addition — mentioned in the MF-8 spec narrative but not in the acceptance criteria; the reviewer agent still submits verdicts via MCP (create_pull_review/submit_pull_review). Tracked for a follow-up once a host-side code path actually needs to submit reviews without going through an agent.new ForgejoAdapter(token)callsites injanitor.ts,deps.ts,pipeline.ts,board.ts,foreman.ts,webhook-handlers.ts,slash-commands.ts,main.ts. Not on the review/merge critical path — touching them here would blow scope. Recommend a dedicated MF-10 story to finish the factory migration across the whole service.forge.jacquin.appURLs in a few places (PR links, review URLs). Display-layer follow-up, not review-flow parity.Closes #299
Adds `ForgePort.mergePullRequest(repo, prNumber, opts)` and implements it on all three adapters so domain code can squash-merge a PR without caring which forge hosts it: - Forgejo: `POST /pulls/{n}/merge` with `Do: "squash"` (title/message forwarded as `MergeTitleField` / `MergeMessageField`). - GitHub: `PUT /pulls/{n}/merge` with `merge_method: "squash"` (title/message forwarded as `commit_title` / `commit_message`). - GitLab: `PUT /merge_requests/{iid}/merge` with `squash: true` (title/message forwarded as `squash_commit_message` / `merge_commit_message`). Per MF-8 spec the squash contract is the project convention — rebase merge, merge-commit and merge queues stay out of scope. `opts.squash` is accepted for port symmetry but the adapter always sends a squash. Also migrates the review/merge-flow `new ForgejoAdapter(token)` instances in `webhook-ci.ts` and `review-loop.ts` onto `createForgeAdapterForRepo(repo, token)` so a PR hosted on GitHub / GitLab picks up the correct adapter transparently. This is a no-op for forgejo-bound repos (factory defaults to Forgejo) and closes the review-flow parity gap from the MF-8 acceptance criteria. Out of scope (flagged as follow-ups): `submitReview` port addition mentioned in the spec narrative, and the remaining `new ForgejoAdapter` callsites in janitor / deps / pipeline / board / foreman / handlers — those are not on the review/merge critical path and touching them would blow this PR's scope. Closes #299