Slice 3: the PR flow

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-05-24 12:37:54 -07:00
parent 33d9d7a482
commit a2bf89e90b
15 changed files with 2928 additions and 141 deletions
+50 -3
View File
@@ -218,13 +218,28 @@ async def refresh_rfc_repo(config: Config, gitea: Gitea, slug: str) -> None:
pull["number"],
pull.get("body") or "",
)
# §10.8: distinguish "user withdrew" from "Gitea closed for any
# other reason." The bot's withdraw action lands in the actions
# log; if we see it, surface state='withdrawn'.
if state == "closed":
withdrew = db.conn().execute(
"""
SELECT 1 FROM actions
WHERE action_kind = 'withdraw_branch_pr'
AND rfc_slug = ? AND pr_number = ? LIMIT 1
""",
(slug, pull["number"]),
).fetchone()
if withdrew:
state = "withdrawn"
merge_commit_sha = pull.get("merge_commit_sha")
db.conn().execute(
"""
INSERT INTO cached_prs
(rfc_slug, pr_kind, repo, pr_number, title, description, state,
opened_by, opened_at, merged_at, closed_at,
head_branch, base_branch, head_sha)
VALUES (?, 'rfc_branch', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
head_branch, base_branch, head_sha, merge_commit_sha)
VALUES (?, 'rfc_branch', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(repo, pr_number) DO UPDATE SET
title = excluded.title,
description = excluded.description,
@@ -232,7 +247,8 @@ async def refresh_rfc_repo(config: Config, gitea: Gitea, slug: str) -> None:
opened_by = excluded.opened_by,
merged_at = excluded.merged_at,
closed_at = excluded.closed_at,
head_sha = excluded.head_sha
head_sha = excluded.head_sha,
merge_commit_sha = COALESCE(excluded.merge_commit_sha, cached_prs.merge_commit_sha)
""",
(
slug,
@@ -248,8 +264,26 @@ async def refresh_rfc_repo(config: Config, gitea: Gitea, slug: str) -> None:
head_branch,
(pull.get("base") or {}).get("ref") or "main",
(pull.get("head") or {}).get("sha"),
merge_commit_sha,
),
)
# §10.9: an explicit `Supersedes: #N` trailer on a merged PR's
# body bumps the predecessor's state to closed and records the
# supersession. The cache propagates this whether the merge came
# via webhook or reconciler.
if state == "merged":
superseded = _parse_supersedes(pull.get("body") or "")
if superseded:
db.conn().execute(
"""
UPDATE cached_prs
SET state = 'closed',
superseded_by_pr_number = ?,
closed_at = COALESCE(closed_at, datetime('now'))
WHERE repo = ? AND pr_number = ? AND state = 'open'
""",
(pull["number"], repo_full, superseded),
)
async def refresh_meta_pulls(config: Config, gitea: Gitea) -> None:
@@ -385,6 +419,19 @@ def _kind_from_branch(head_branch: str) -> str:
return "idea" # fallback
_SUPERSEDES_RE = None
def _parse_supersedes(body: str) -> int | None:
"""Parse a `Supersedes: #N` trailer from a PR body per §10.9."""
import re as _re
global _SUPERSEDES_RE
if _SUPERSEDES_RE is None:
_SUPERSEDES_RE = _re.compile(r"^Supersedes:\s*#(\d+)", _re.MULTILINE)
m = _SUPERSEDES_RE.search(body or "")
return int(m.group(1)) if m else None
def _state_from_pull(pull: dict) -> str:
if pull.get("merged"):
return "merged"