feat(skill): extract review-merge-pr workflow from monolithic SKILL.md

Closes #334

- Add workflows/review-merge-pr.md with reviewer-only gates and steps
- Add schemas/review-merge-final-report.md with mutation ledger fields
- Turn SKILL.md into task-mode router; replace inlined §F/§G with links
- Point review/merge templates at extracted workflow
- Add doc-contract tests for split structure

Part of #333.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
2026-07-07 03:32:59 -04:00
co-authored by Claude Opus 4.8
parent e4d78a5013
commit 3976f886e4
6 changed files with 447 additions and 104 deletions
+36 -104
View File
@@ -17,6 +17,30 @@ The core promise: **an LLM never does unsafe or untracked work.** Every change
is tracked by an issue, isolated in its own worktree, reviewed by a different
identity, and cleaned up only after a real merge.
## Task mode router (#333)
**Identify task mode before any mutation.** Load the matching workflow file and
final-report schema; do not apply rules from other modes.
| Task mode | Workflow | Final report schema |
|-----------|----------|---------------------|
| `review-merge-pr` | [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) | [`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md) |
| `create-issue` | `workflows/create-issue.md` (planned — #333) | `schemas/create-issue-final-report.md` (planned) |
| `work-issue` | `workflows/work-issue.md` (planned — #333) | `schemas/work-issue-final-report.md` (planned) |
Mode isolation:
- **review-merge-pr** — reviewer namespace only; no issue creation, no code edits, no approve/merge replay after terminal review
- **create-issue** — author namespace; duplicate search + `create_issue` capability; no PR review/merge
- **work-issue** — author namespace; implement, validate, commit, PR; no approve/merge
Shared gate references (all modes): `gates/identity-capability-rules.md`,
`gates/mutation-ledger-rules.md`, `gates/proof-wording-rules.md` (planned — #333).
Until `create-issue.md` and `work-issue.md` land, author rules remain in this
file (§A, §E) and review rules live in
[`workflows/review-merge-pr.md`](workflows/review-merge-pr.md).
---
@@ -283,110 +307,16 @@ Worktree folder = branch with `/` replaced by `-`
## F. Review workflow
1. Use a separate review worktree (`scripts/worktree-review <branch>`), detached.
2. Verify your authenticated identity.
3. Verify the PR author — **you must not be the author.** Self-review
contamination must be *evidence-backed* (#173): state the authenticated
reviewer identity, the PR author identity, whether this session
authored/touched the PR branch, and the evidence source for any
"same-session author" claim. If the evidence is missing, report the
status as **unknown** — never declare contamination by assumption — and
choose another PR or stop (`review_proofs.assess_self_review_contamination`).
4. Verify the worktree is clean.
5. **Checkout proof (#173):** before reviewing or validating, prove and
state: the selected PR head SHA from Gitea (pinned), the local checkout
SHA (`git rev-parse HEAD`), that `HEAD ==` the pinned PR head SHA, and
that the diff base is the PR base branch. If `HEAD` does not match the
pinned head, **stop before review/merge**
(`review_proofs.verify_pinned_head_checkout`).
6. **Inventory proof (#173 + repo disambiguation hardening):** a blind queue
review must prove listing completeness before claiming "only PRs found".
Use repo-name disambiguation:
- "Gitea-Tools" / "gitea tool" / "MCP Gitea tool" / "gitea MCP tool" /
"gitea-tools repo" resolve **only** to `Scaled-Tech-Consulting/Gitea-Tools`.
- "mcp-control-plane" resolves only to `Scaled-Tech-Consulting/mcp-control-plane`.
- Ambiguous ("open PRs", no explicit repo, "MCP Gitea tooling") → inventory
**both** configured repos.
Report must state exactly which repo(s) were checked. If only one checked:
"Only <repo> was checked. Other configured repos were not checked. This is
not a complete queue inventory." Never let a single-repo zero hide PRs in
the other.
Both configured repos must be reported with state filter, pagination proof,
and open-PR count (`review_proofs.assess_inventory_completeness` and
`resolve_repos_from_user_reference`). Before inventory, reconcile the
operator-supplied PR backlog against the target repo
(`review_proofs.reconcile_queue_target`); never report `trusted_empty`
for one repo while ignoring contradictory supplied PR numbers in another.
7. **Role-boundary proof (#175):** a reviewer queue task must not silently
become author implementation. If no eligible PR exists, stop with the
queue report. Do not claim issues, create branches, commit, push, or open
PRs unless the operator explicitly retasks the run as author work. Mixed
reviewer+author namespace use must be reported with a justification, and
scratch-only notes are not durable evidence unless posted or committed
intentionally (`review_proofs.assess_role_boundary`).
8. Inspect the full diff; confirm scope matches the linked issue; flag unrelated files.
9. Run the tests. Validation reporting must include the exact command and
exact results: pass/fail, counts of tests passed/skipped/failed, any
ignored paths and why they are safe to ignore, and whether the command
differs from the repository's canonical validation command. Only claim a
validation result after the command has completed and its output has
been read (`review_proofs.assess_validation_report`).
During validation, review work is **read-only**: use
`gitea_dry_run_pr_review` to prove submission mechanics — never post live
APPROVE, REQUEST_CHANGES, or review comments to probe tool paths. After
validation completes, call `gitea_mark_final_review_decision`, then submit
exactly one live review via
`gitea_submit_pr_review(..., final_review_decision_ready=True)`.
After submitting, re-read `gitea_get_pr_review_feedback` and confirm the
verdict is visible (`approval_visible` true for APPROVE; PENDING drafts do
not count — #244). Do not merge until a visible APPROVED review exists.
Final reports must list exactly one review mutation
(`review_proofs.assess_review_mutation_final_report`) unless an
operator-approved correction flow was invoked and explained.
10. **Do not merge if checks fail. Do not merge if the reviewer is the author.**
11. **#179 A-bar proofs** (all fail closed when missing —
`review_proofs.assess_capability_evidence`, `assess_sweep_evidence`,
`assess_live_state_recheck`, `assess_role_boundary`):
- Capability claims must cite exact `gitea_resolve_task_capability`
output (or runtime context); a bare "capability checks passed" is
downgraded.
- The secret/provenance sweep must state the exact command/script/
pattern/named method and the scope scanned.
- Immediately before submitting a review verdict (and again before any
merge), re-read live PR state and prove: still open, live head ==
pinned head, base unchanged, no unresolved blocking review state.
- Reviewer runs stay in the reviewer namespace; any author-namespace
call requires an explicit justification in the report.
12. The final report must distinguish (`review_proofs.build_final_report`):
identity eligible; PR author different from reviewer; session
contamination absent (with evidence); validation performed on the pinned
head; capability evidence; sweep verdict; live-state recheck; role
boundary; merge performed; issue status verified. If any proof is
missing, stop or downgrade the result instead of merging confidently.
Moved to [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) (#334).
Load that file for PR inventory, validation, review mutation, merge preflight,
cleanup, and reviewer hard walls. Final report:
[`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md).
## G. Merge / cleanup workflow
Only an eligible (non-author) reviewer merges. Before merging: always verify
the authenticated identity **and** the PR author; cite exact capability
evidence for merge_pr (#179); respect runtime profile gates; run independent
validation (do not trust the author's reported results); perform the **final
live-state recheck** (#179 — PR still open, live head == pinned head, base
unchanged, no unresolved blocking review state) immediately before the merge
mutation; and merge with a **pinned head SHA** and, where supported, the
**expected changed-file set**, so a moved head or widened diff refuses the
merge. After a real merge:
1. Confirm remote `master` actually contains the merge commit or expected squashed changes via post-merge file-presence verification (A PR is not done just because `master` moved or is marked "closed". Verify that expected files added/modified in the PR are actually present on `master` using `git pull`, `git log --oneline -- <file>`, or `git merge-base --is-ancestor`; linked issues are closed; `status:in-progress` is removed).
2. Close/release the issue.
3. Whenever an issue is closed, check for `status:in-progress`: remove it, or report why it could not be removed.
4. Do not delete the remote source branch until: PR `merged=true`, or reconciliation confirms content is safely landed, or the issue owner explicitly abandons the work.
5. Remove the local branch.
6. Remove the branch worktree folder (`scripts/worktree-clean --delete-branch <branch>`). Branches/worktrees are cleaned only after the above is verified.
7. Fetch/prune.
8. Confirm the main checkout is clean and current (`0 0` vs remote).
9. Final merge/reconciliation reports must include: PR metadata (state, merged flag, merge commit/hash), Git content (remote master hash, expected content present or not), and the exact post-merge verification method used & results.
Never run cleanup before the merge is confirmed on remote `master`.
Moved to [`workflows/review-merge-pr.md`](workflows/review-merge-pr.md) §6–§7
and [`templates/worktree-cleanup.md`](templates/worktree-cleanup.md). Load the
workflow file before any merge mutation.
## H. Fail-closed cases
@@ -479,9 +409,11 @@ Compact format (default, canonical field set per issue #182):
Role-specific fields (append to the compact block):
- review/merge tasks: `Selected PR:`, `Reviewer eligibility:`,
`Pinned reviewed head:`, `Review decision:`, `Merge result:`,
`Linked issue status:`, `Cleanup status:`
- review/merge tasks: use
[`schemas/review-merge-final-report.md`](schemas/review-merge-final-report.md)
(includes `Selected PR:`, `Reviewer eligibility:`, `Pinned reviewed head:`,
`Review decision:`, `Merge result:`, `Linked issue status:`, `Cleanup status:`,
and precise mutation categories — not `Workspace mutations`)
- issue-filing tasks (#191): `Issue created or updated:`, `Related issues:`;
body must cite exact issue number/title, duplicate-search summary (issues
searched, closest matches, why update rejected / new issue justified), full
@@ -0,0 +1,113 @@
# Review-merge final report schema
**Task mode:** `review-merge-pr`
Every review or merge run must end with a section titled exactly
`Controller Handoff` validated by `review_proofs.assess_controller_handoff`.
Use this schema — not author or issue-filing fields (`PR number opened`,
`Claim/comment status`, `Workspace mutations`).
## Required header
```md
## Controller Handoff
- Task: review-merge-pr
- Repo:
- Role/profile:
- Identity:
- Mode: review-merge-pr
```
Identity format: `username / profile` (e.g. `sysadmin / prgs-reviewer`). Do not
include personal email unless required for disambiguation (#305).
## Selection and eligibility
```md
- Selected PR:
- PR live state:
- Pinned reviewed head:
- Reviewer eligibility:
- Repositories checked:
- Open PR counts:
- Inventory completeness:
- Inventory pagination proof:
- Selected PR or reason none selected:
- Linked issue:
- Linked issue live status:
```
Linked issue number must match across all sections (#314). If not verified live:
`Linked issue status: not verified in this session`.
## Validation proof (#311, #323)
```md
- Validation command:
- Validation working directory:
- Validation executable proof:
- Validation result:
- Baseline SHA: (required if full suite failed on PR head)
- Baseline validation command/result: (required if approving despite full-suite failure)
- Matching failure list:
```
Reject vague claims like "same as master" without baseline worktree proof.
## Review and merge outcome
```md
- Review decision:
- Review mutations:
- Merge result:
- Merge commit:
- Cleanup status:
```
`Review mutations` must list exactly one live terminal review unless
operator-approved correction is documented.
## Mutation ledger (#319, #320)
Use precise categories — **not** `Workspace mutations` or bare `Mutations: None`.
```md
- File edits by reviewer: none
- Worktree/index mutations:
- Git ref mutations:
- MCP/Gitea mutations:
- Review mutations:
- Merge mutations:
- Cleanup mutations:
- External-state mutations:
- Read-only diagnostics:
```
Rules:
- `git fetch` → Git ref mutations
- `git worktree add/remove` → Worktree/index mutations
- merge simulation → Worktree/index mutations (#317)
- MCP review/merge calls → MCP/Gitea mutations + Review/Merge mutations
## Proof wording (#330)
Do not claim "live proof" for blockers unless revalidated this session. Prior
feedback at unchanged head: say `prior blocker reused` or `prior request-changes
still applies`.
Reject unsupported phrases: `inventory complete`, `same as master`, `live proof`,
`file edits none` — unless matching evidence is present.
## Blockers and safety
```md
- Blockers:
- Safe next action:
- Safety: no self-review; no self-merge; no author mutations; no code edits
```
Long format (§K in SKILL.md) is required when merge occurred, validation failed,
permissions blocked, or multiple repos were involved.
@@ -5,6 +5,10 @@ Copy, fill the `<...>` fields, and paste as the task prompt.
```text
Task: merge PR #<pr> for issue #<n> if it is eligible and checks pass.
Load the canonical workflow first:
`skills/llm-project-workflow/workflows/review-merge-pr.md` (task mode: review-merge-pr).
Final report schema: `schemas/review-merge-final-report.md`.
Rules (llm-project-workflow):
- Only an eligible, NON-author reviewer merges. If authenticated user == PR
author → STOP.
@@ -30,6 +30,10 @@ Repo name disambiguation (Gitea-Tools blind review hardening):
commit is not valid corroboration. Author-bound sessions must not present
reviewer queue inventory as a reviewer decision.
Load the canonical workflow first:
`skills/llm-project-workflow/workflows/review-merge-pr.md` (task mode: review-merge-pr).
Final report schema: `schemas/review-merge-final-report.md`.
Rules (llm-project-workflow):
- Review in a SEPARATE detached review worktree, never the author's folder.
- Worktree safety (#233): before checkout, diff, validation, review, or merge,
@@ -0,0 +1,243 @@
# Review and merge PR workflow
**Task mode:** `review-merge-pr`
Load this file before any PR review or merge mutation. Parent router:
[`../SKILL.md`](../SKILL.md). Final report schema:
[`../schemas/review-merge-final-report.md`](../schemas/review-merge-final-report.md).
## Mode isolation (hard walls)
Review mode **must not**:
- create issues or edit issue bodies
- edit repository files, run formatting, or fix code
- claim issues, create branches, commit, push, or open PRs
- use author MCP namespace (`gitea-tools` / `prgs-author`) for mutations
- improvise raw MCP repair, shell fallbacks, or background task tools
Review mode **must**:
- use reviewer MCP namespace (`gitea-reviewer` / `prgs-reviewer`)
- prove identity, profile, runtime context, and exact capabilities before mutation
- validate only in a session-owned worktree under `branches/`
- keep the main checkout on `master`/`main`/`dev` for orchestration only
If the authenticated user equals the PR author → **stop** (no self-review, no self-merge).
---
## Global worktree rule (reviewer)
The main project checkout is orchestration only. All review validation happens
under `branches/` — typically a detached review worktree from
`scripts/worktree-review <pr-head-branch>`.
Before validation, prove:
1. project root
2. working directory (must be under `branches/`)
3. worktree path
4. worktree clean status
5. pinned PR head SHA == `git rev-parse HEAD`
Never run `git stash`, `git checkout --`, `git reset`, or `git clean` on
another session's dirty files. If the shared worktree is dirty, create a scratch
review worktree instead.
Merge simulation (`git merge --no-commit`, `git merge --abort`) is a
**worktree/index mutation**, not read-only diagnostics (#317).
---
## 1. Pre-flight gates
Run before any PR mutation:
1. `gitea_whoami` — prove reviewer identity (not PR author)
2. `gitea_get_profile` / `gitea_get_runtime_context`
3. `gitea_resolve_task_capability` for `review_pr`, `approve_pr`, `merge_pr`
4. `gitea_check_pr_eligibility` for the intended action on the selected PR
Capability claims must cite exact resolver output (#179); bare "checks passed" is
downgraded.
Stay in the reviewer namespace. Any author-namespace call requires explicit
justification in the final report.
---
## 2. PR inventory and queue selection
### Repo disambiguation
- `Gitea-Tools` / `gitea tool` / `MCP Gitea tool``Scaled-Tech-Consulting/Gitea-Tools`
- `mcp-control-plane``Scaled-Tech-Consulting/mcp-control-plane`
- Ambiguous requests → inventory **both** configured repos
Report must state exactly which repo(s) were checked. A single-repo zero must not
be reported as a global empty queue if the other repo was not inventoried.
### Pagination proof (#330)
PR inventory completeness requires explicit final-page/no-next-page evidence,
total-count evidence, or traversal until an empty/final page. Do not prove
completeness by assuming the default Gitea API page size.
### Trust gate (#196, #198)
Before reporting "no open PRs" or "queue empty":
- run `pr_inventory_trust_gate` (or `review_proofs.assess_reviewer_queue_inventory`)
- only `trusted_empty` allows a clean empty-queue stop
- a bare `[]` from `gitea_list_prs` is never sufficient
Empty-queue reports must include `pr_inventory_trust_gate.status`, reasons,
corroboration, remote/owner/repo/state filter, and inventory MCP profile.
### Queue ordering (#321, #318)
Select the lowest eligible open PR unless a documented blocker requires skipping.
Skipping requires live proof the blocker still applies — not stale handoff text.
### Already-landed gate (#292, #294, #295)
If the PR head is already on the target branch, stop review/approve/merge.
Redirect to reconciliation workflow; do not treat as review-eligible.
---
## 3. Review worktree and pinned head
1. `scripts/worktree-review <pr-head-branch>` → detached under `branches/review-*`
2. Prove checkout (#173):
- pinned PR head SHA from Gitea
- local `git rev-parse HEAD`
- `HEAD ==` pinned head
- diff base == PR base branch
3. If head moved → re-pin and restart review
4. Confirm worktree clean; inspect full diff; confirm scope matches linked issue
Linked issue consistency (#314): fetch/verify the linked issue live. Report the
same issue number in all sections. If not verified:
`Linked issue status: not verified in this session`.
Secret/provenance sweep (#179): state exact command/script/pattern and scope.
---
## 4. Validation
Run tests from the review worktree. Report must include (#311):
- exact command as executed
- working directory
- exact result (pass/fail counts)
- executable path proof for bare commands like `pytest` (`which`, `--version`)
If full suite fails, default action is `REQUEST_CHANGES` (#323). Approval with
failures requires baseline worktree proof under `branches/`, not main checkout.
During validation, use `gitea_dry_run_pr_review` — never live APPROVE/REQUEST_CHANGES
to probe tool paths.
---
## 5. Review mutation (terminal)
After validation completes:
1. Re-fetch PR; verify head SHA unchanged
2. `gitea_mark_final_review_decision` with pinned head
3. `gitea_submit_pr_review(..., final_review_decision_ready=True)` — exactly one live review
4. `gitea_get_pr_review_feedback` — confirm visible verdict (`approval_visible` for APPROVE; PENDING drafts do not count — #244)
Final reports must list exactly one review mutation
(`review_proofs.assess_review_mutation_final_report`).
Hard-stop after terminal review mutation (#332): do not perform additional review
mutations in the same session unless operator-approved correction.
---
## 6. Merge preflight and execution
Merge only when:
- eligible non-author reviewer
- visible APPROVED review at pinned head
- no undismissed REQUEST_CHANGES
- PR open and mergeable
- live head == pinned reviewed head (re-fetch immediately before merge)
Use `gitea_merge_pr` with `confirmation="MERGE PR <n>"` and `expected_head_sha`.
Gated merge refuses moved heads and widened diffs when `expected_changed_files` is set.
---
## 7. Post-merge cleanup
After confirmed merge on remote `master`:
1. Post-merge file-presence verification on `master`
2. Close/release linked issue; remove `status:in-progress`
3. Delete remote branch (when safe)
4. `scripts/worktree-clean --delete-branch <branch>`
5. Fetch/prune; confirm main checkout clean and `0 0` vs remote
Never run cleanup before merge is confirmed on remote `master`.
See also [`../templates/worktree-cleanup.md`](../templates/worktree-cleanup.md).
---
## 8. Fail-closed stops
Stop and report (no mutating action) when:
- identity/capability cannot be proven
- self-author detected
- worktree state unclear
- pinned head mismatch
- inventory incomplete or untrusted
- tests fail without baseline proof
- PR closed-not-merged (`merged=false`) — run reconciliation instead
- infra_stop or MCP transport failure — recovery handoff only
When blocked, emit recovery handoff per
[`../templates/recover-bad-state.md`](../templates/recover-bad-state.md).
---
## 9. Role-boundary proof (#175)
A reviewer queue task must not silently become author implementation. If no
eligible PR exists, stop with the queue report. Do not claim issues, branch,
commit, push, or open PRs unless the operator explicitly retasks as author work.
Use `review_proofs.assess_role_boundary`; downgrade or stop if not clean.
---
## 10. A-bar proofs (#179)
All required before A-grade report or merge:
- `assess_capability_evidence`
- `assess_sweep_evidence`
- `assess_live_state_recheck` (immediately before review submit and merge)
- `assess_role_boundary`
- `assess_review_mutation_final_report`
- `build_final_report`
Missing proof → downgrade or block merge.
---
## Prompt templates
Short triggers still work via templates that reference this workflow:
- [`../templates/review-pr.md`](../templates/review-pr.md)
- [`../templates/merge-pr.md`](../templates/merge-pr.md)
+47
View File
@@ -0,0 +1,47 @@
"""Doc-contract checks for task-specific LLM workflow split (#333, #334)."""
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
SKILL_DIR = REPO_ROOT / "skills" / "llm-project-workflow"
def test_review_merge_workflow_file_exists():
path = SKILL_DIR / "workflows" / "review-merge-pr.md"
text = path.read_text(encoding="utf-8")
assert "Task mode:" in text
assert "review-merge-pr" in text
assert "gitea-reviewer" in text
assert "scripts/worktree-review" in text
def test_review_merge_final_report_schema_exists():
path = SKILL_DIR / "schemas" / "review-merge-final-report.md"
text = path.read_text(encoding="utf-8")
assert "Controller Handoff" in text
assert "Worktree/index mutations" in text
assert "Workspace mutations" in text # documented as rejected
def test_skill_router_points_to_review_merge_workflow():
skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
assert "## Task mode router" in skill
assert "workflows/review-merge-pr.md" in skill
assert "schemas/review-merge-final-report.md" in skill
assert "Identify task mode before any mutation" in skill
def test_skill_still_declares_controller_handoff_contract():
skill = (SKILL_DIR / "SKILL.md").read_text(encoding="utf-8")
assert "## Controller Handoff" in skill
assert "assess_controller_handoff" in skill
assert "## Global LLM Worktree Rule" in skill
def test_review_pr_template_references_extracted_workflow():
text = (SKILL_DIR / "templates" / "review-pr.md").read_text(encoding="utf-8")
assert "workflows/review-merge-pr.md" in text
def test_merge_pr_template_references_extracted_workflow():
text = (SKILL_DIR / "templates" / "merge-pr.md").read_text(encoding="utf-8")
assert "workflows/review-merge-pr.md" in text