feat: add first-class stacked PR support to author workflow (Closes #484) #489

Merged
sysadmin merged 1 commits from feat/issue-484-stacked-pr-support into master 2026-07-08 01:41:19 -05:00
Owner

Summary

Closes #484.

Adds a first-class, proof-backed stacked PR path to the author workflow so gitea_create_pr can open a PR based on another unmerged PR's branch — without retargeting to master (which would entangle the dependency's diff) or bypassing the issue-lock / base-branch gate.

Normal master-based safety gates are unchanged: when no stacked base is declared, gitea_lock_issue still requires a master/main/dev base worktree and gitea_create_pr still targets a normal base branch.

Motivating case

#482 (stacked on #479 / #478) could not be opened via MCP because the lock required a master/main/dev base worktree. That PR had to be opened manually. This change makes that flow first-class.

Design

  • stacked_pr_support.py (new, pure): approve a non-master base only when it is explicitly declared AND owned by a live open PR whose number matches; reject arbitrary, mismatched, or stale (merged/closed) bases; require the PR body to document the stack.
  • issue_lock_worktree.py: read_worktree_git_state / _find_matching_base_ref gain an opt-in extra_bases arg so an approved stacked base can anchor the base check alongside master/main/dev. Empty by default → behavior unchanged.
  • gitea_mcp_server.py:
    • gitea_lock_issue gains stacked_base_branch + stacked_base_pr. When declared, it verifies the open PR owns the branch, anchors the base check to it, and records approved_stacked_base = {branch, pr_number, verified_open} on the lock.
    • gitea_create_pr validates a non-master base against the lock's approved stacked base, re-checks the dependency PR is still open, and enforces the stacked-PR body fields. Master-based path untouched.
  • Docs: docs/llm-workflow-runbooks.md and skills/llm-project-workflow/workflows/work-issue.md document when stacked PRs are allowed and the required body wording (Stacked on PR #X / issue #Y, Base branch, Head branch, Do not merge before PR #X, retarget-after-land).

Constraints honored

  • Normal flow still requires a master/main/dev base (unchanged).
  • Stacked support never bypasses the issue lock — the base is recorded on the lock and re-verified at PR time.
  • Arbitrary / stale / merged dependency bases fail closed.
  • Root-checkout stability guard (#475) untouched.

Tests

  • tests/test_stacked_pr_support.py — 18 policy cases (declaration proof, arbitrary/stale rejection, PR-number mismatch, body-field enforcement, master path unchanged, create-time re-verification).
  • tests/test_issue_lock_worktree.py — stacked base anchoring via extra_bases (real temp git repo); a non-matching base does not anchor.
  • tests/test_issue_lock_store.pyapproved_stacked_base persistence round-trip.

Validation

Run in branches/issue-484-stacked-pr-support @ 0cbd1fc:

python -m py_compile stacked_pr_support.py issue_lock_worktree.py gitea_mcp_server.py   # OK
python -m pytest tests/test_stacked_pr_support.py tests/test_issue_lock_worktree.py tests/test_issue_lock_store.py -q   # 45 passed
python -m pytest tests/ -q   # 1753 passed, 6 skipped
git diff --check   # clean

Note: one separate test, TestMergePR::test_merge_blocked_on_stale_approval_head, fails on pristine master too (verified by stashing this branch's edits) — a pre-existing failure in merge-approval code, out of scope here.

Worktree

branches/issue-484-stacked-pr-support @ 0cbd1fc

## Summary Closes #484. Adds a first-class, proof-backed **stacked PR** path to the author workflow so `gitea_create_pr` can open a PR based on another unmerged PR's branch — without retargeting to `master` (which would entangle the dependency's diff) or bypassing the issue-lock / base-branch gate. Normal master-based safety gates are **unchanged**: when no stacked base is declared, `gitea_lock_issue` still requires a `master`/`main`/`dev` base worktree and `gitea_create_pr` still targets a normal base branch. ## Motivating case #482 (stacked on #479 / #478) could not be opened via MCP because the lock required a `master`/`main`/`dev` base worktree. That PR had to be opened manually. This change makes that flow first-class. ## Design - **`stacked_pr_support.py`** (new, pure): approve a non-master base only when it is explicitly declared AND owned by a live **open** PR whose number matches; reject arbitrary, mismatched, or stale (merged/closed) bases; require the PR body to document the stack. - **`issue_lock_worktree.py`**: `read_worktree_git_state` / `_find_matching_base_ref` gain an opt-in `extra_bases` arg so an approved stacked base can anchor the base check alongside master/main/dev. Empty by default → behavior unchanged. - **`gitea_mcp_server.py`**: - `gitea_lock_issue` gains `stacked_base_branch` + `stacked_base_pr`. When declared, it verifies the open PR owns the branch, anchors the base check to it, and records `approved_stacked_base = {branch, pr_number, verified_open}` on the lock. - `gitea_create_pr` validates a non-master `base` against the lock's approved stacked base, re-checks the dependency PR is still open, and enforces the stacked-PR body fields. Master-based path untouched. - **Docs**: `docs/llm-workflow-runbooks.md` and `skills/llm-project-workflow/workflows/work-issue.md` document when stacked PRs are allowed and the required body wording (`Stacked on PR #X / issue #Y`, `Base branch`, `Head branch`, `Do not merge before PR #X`, retarget-after-land). ## Constraints honored - Normal flow still requires a `master`/`main`/`dev` base (unchanged). - Stacked support **never bypasses the issue lock** — the base is recorded on the lock and re-verified at PR time. - Arbitrary / stale / merged dependency bases fail closed. - Root-checkout stability guard (#475) untouched. ## Tests - `tests/test_stacked_pr_support.py` — 18 policy cases (declaration proof, arbitrary/stale rejection, PR-number mismatch, body-field enforcement, master path unchanged, create-time re-verification). - `tests/test_issue_lock_worktree.py` — stacked base anchoring via `extra_bases` (real temp git repo); a non-matching base does not anchor. - `tests/test_issue_lock_store.py` — `approved_stacked_base` persistence round-trip. ## Validation Run in `branches/issue-484-stacked-pr-support` @ `0cbd1fc`: ``` python -m py_compile stacked_pr_support.py issue_lock_worktree.py gitea_mcp_server.py # OK python -m pytest tests/test_stacked_pr_support.py tests/test_issue_lock_worktree.py tests/test_issue_lock_store.py -q # 45 passed python -m pytest tests/ -q # 1753 passed, 6 skipped git diff --check # clean ``` Note: one separate test, `TestMergePR::test_merge_blocked_on_stale_approval_head`, fails on pristine `master` too (verified by stashing this branch's edits) — a pre-existing failure in merge-approval code, out of scope here. ## Worktree `branches/issue-484-stacked-pr-support` @ `0cbd1fc`
jcwalker3 added 1 commit 2026-07-08 01:24:59 -05:00
Normal author work is unchanged: gitea_lock_issue still requires the worktree
to be base-equivalent to master/main/dev, and gitea_create_pr still targets a
normal base branch. A *stacked* PR (based on another unmerged PR's branch) is a
new explicit, proof-backed path — it never bypasses the issue lock.

- stacked_pr_support.py: pure policy. Approve a non-master base only when it is
  declared AND owned by a live OPEN PR whose number matches; reject arbitrary,
  mismatched, or stale (merged/closed) branches; require the PR body to document
  the stack (base branch, stacked-on PR, merge ordering).
- issue_lock_worktree.py: read_worktree_git_state / _find_matching_base_ref gain
  an opt-in extra_bases arg so an approved stacked base can anchor
  base-equivalence in addition to master/main/dev (empty by default = unchanged).
- gitea_mcp_server.py:
  - gitea_lock_issue gains stacked_base_branch + stacked_base_pr. When declared,
    it verifies the open PR owns the branch, anchors base-equivalence to it, and
    records approved_stacked_base = {branch, pr_number, verified_open} on the lock.
  - gitea_create_pr validates a non-master base against the lock's approved
    stacked base, re-checks the dependency PR is still open, and enforces the
    stacked-PR body fields. Master-based path untouched.
- Docs: llm-workflow-runbooks.md and work-issue.md document when stacked PRs are
  allowed and the required PR-body wording.

Tests: test_stacked_pr_support.py (18 policy cases), stacked base-equivalence in
test_issue_lock_worktree.py, approved_stacked_base persistence round-trip in
test_issue_lock_store.py. Existing lock/create_pr regressions still pass.

The #482 -> #479/#478 case motivates this: create_pr previously could not open a
stacked PR because the lock required a master-equivalent worktree.

Closes #484.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #489
issue: #484
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 96981-2106e7a7d1bf
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr489
phase: claimed
candidate_head: 0cbd1fc801
target_branch: master
target_branch_sha: none
last_activity: 2026-07-08T06:34:56Z
expires_at: 2026-07-08T08:34:56Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #489 issue: #484 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 96981-2106e7a7d1bf worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr489 phase: claimed candidate_head: 0cbd1fc801e7490a8faa9b21b89cf51d269c9830 target_branch: master target_branch_sha: none last_activity: 2026-07-08T06:34:56Z expires_at: 2026-07-08T08:34:56Z blocker: none
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #489
issue: none
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 96981-2106e7a7d1bf
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr489
phase: claimed
candidate_head: 0cbd1fc801
target_branch: master
target_branch_sha: none
last_activity: 2026-07-08T06:38:38Z
expires_at: 2026-07-08T08:38:38Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #489 issue: none reviewer_identity: sysadmin profile: prgs-reviewer session_id: 96981-2106e7a7d1bf worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr489 phase: claimed candidate_head: 0cbd1fc801e7490a8faa9b21b89cf51d269c9830 target_branch: master target_branch_sha: none last_activity: 2026-07-08T06:38:38Z expires_at: 2026-07-08T08:38:38Z blocker: none
sysadmin approved these changes 2026-07-08 01:38:47 -05:00
sysadmin left a comment
Owner

APPROVED: PR #489 stacked PR support pure logic, tests, and integration are clean, robust, and fully verified.

APPROVED: PR #489 stacked PR support pure logic, tests, and integration are clean, robust, and fully verified.
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #489
issue: #484
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 96981-2106e7a7d1bf
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr489
phase: abandoned
candidate_head: 0cbd1fc801
target_branch: master
target_branch_sha: none
last_activity: 2026-07-08T06:39:41Z
expires_at: 2026-07-08T08:39:41Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #489 issue: #484 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 96981-2106e7a7d1bf worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr489 phase: abandoned candidate_head: 0cbd1fc801e7490a8faa9b21b89cf51d269c9830 target_branch: master target_branch_sha: none last_activity: 2026-07-08T06:39:41Z expires_at: 2026-07-08T08:39:41Z blocker: none
sysadmin merged commit 974463163d into master 2026-07-08 01:41:19 -05:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#489