feat: enforce no-self-merge rule across all team scripts (#585)

Agents can self-review their PRs (read diff, add comments) but must
never merge. PRs get labeled `needs-team-review` and stay open for
external review by maintainers or a separate review cycle.

Changes across all three scripts:
- refactor.sh: added No Self-Merge Rule section, updated worktree
  pattern, issue fix workflow, monitoring loop, and lifecycle mgmt
- discovery.sh: added No Self-Merge Rule section, updated worktree
  pattern, git workflow, branch cleaner, and lifecycle mgmt
- qa-cycle.sh: renamed push_and_merge_pr to push_and_create_pr,
  removed all gh pr merge calls, added self-review + labeling

Agent: team-lead

Co-authored-by: Sprite <noreply@sprite.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
L 2026-02-11 23:02:32 -08:00 committed by GitHub
parent 1ad2371a25
commit 627abd2fb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 122 additions and 75 deletions

View file

@ -157,6 +157,20 @@ Each cycle MUST complete within 45 minutes. This is a HARD deadline.
Agents should aim for focused, high-impact work. Do NOT exhaustively expand everything.
## No Self-Merge Rule (MANDATORY)
Agents must NEVER merge their own PRs. This applies to ALL agents including the team lead.
After creating a PR, every agent MUST:
1. **Self-review**: Read the diff and add a review comment summarizing changes, tests run, and any concerns:
`gh pr diff NUMBER --repo OpenRouterTeam/spawn`
`gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by AGENT-NAME: [summary of changes, what was tested, any concerns]"`
2. **Label**: Add `needs-team-review` so external reviewers can find it:
`gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"`
3. **Leave the PR open** — do NOT run `gh pr merge`
Merging is handled externally (by maintainers or a separate review cycle).
## Priority Order
1. **Fill gaps first** — if manifest.json has "missing" entries, fill them before discovering
@ -213,7 +227,7 @@ Research new AI agents, BUT only add one if there's REAL community demand:
Check the repo's GitHub issues for user requests:
- Run: `gh issue list --repo OpenRouterTeam/spawn --state open --limit 20`
- Look for issues requesting specific agents or cloud providers
- If a request is actionable, implement it
- If a request is actionable, implement it and create a PR (self-review + label, do NOT merge)
- Comment on the issue with the PR link when done
- If a request is already implemented, close the issue with a comment
@ -223,8 +237,8 @@ Clean up stale remote branches before and after the cycle:
- For each branch (excluding main):
* Check if there's an open PR: `gh pr list --head BRANCH --state open --json number,title`
* If open PR and branch is stale (last commit >4 hours ago):
- Mergeable → merge with `gh pr merge NUMBER --squash --delete-branch`
- Conflicts/failing → close with `gh pr close NUMBER --comment "Auto-closing: stale branch. Please reopen if still needed."`
- If PR has conflicts/failing checks → close with `gh pr close NUMBER --comment "Auto-closing: stale branch. Please reopen if still needed."`
- If PR is mergeable → ensure it has `needs-team-review` label, add a comment noting it's stale (do NOT merge — merging is external)
* If no open PR and stale >4 hours → delete with `git push origin --delete BRANCH`
* If fresh (<4 hours) → leave alone
- Run again at end of cycle to catch branches created during the cycle
@ -311,11 +325,15 @@ Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>"
# 5. Push
git push -u origin BRANCH-NAME
# 6. Create and merge PR (can be done from anywhere)
# 6. Create PR (can be done from anywhere)
gh pr create --title "title" --body "body"
gh pr merge NUMBER --squash --delete-branch
# 7. Clean up worktree
# 7. Self-review and label (DO NOT merge — see No Self-Merge Rule)
gh pr diff NUMBER --repo OpenRouterTeam/spawn
gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by AGENT-NAME: [summary]"
gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"
# 8. Clean up worktree (PR stays open for external review)
git worktree remove WORKTREE_BASE_PLACEHOLDER/BRANCH-NAME
```
@ -342,25 +360,28 @@ Every teammate MUST follow this workflow using worktrees. NO exceptions.
4. Do the work, commit (with Agent: marker)
5. Push: `git push -u origin {branch-name}`
6. Create PR: `gh pr create --title "..." --body "..."`
7. Try to merge: `gh pr merge --squash --delete-branch`
8. **If merge fails** (conflicts, CI, etc.):
- Comment on the PR explaining WHY it cannot be merged
7. Self-review and label (DO NOT merge):
`gh pr diff NUMBER --repo OpenRouterTeam/spawn`
`gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by AGENT-NAME: [summary]"`
`gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"`
8. **If PR cannot be created** (conflicts, etc.):
- Comment on the PR explaining WHY
- Close with: `gh pr close {number} --comment "Closing: {reason}"`
- Acceptable reasons: merge conflict with a concurrent PR, superseded by another PR, implementation found to be incorrect after review
- NEVER close a PR silently — every closed PR MUST have a comment
9. Clean up worktree: `git worktree remove WORKTREE_BASE_PLACEHOLDER/{branch-name}`
### PR Policy (MANDATORY):
Every PR must reach one of these terminal states:
- **MERGED** — the happy path, always preferred
- **CLOSED with comment** — only when merge is impossible, with a clear explanation
- **OPEN with self-review + `needs-team-review` label** — the standard path, always preferred
- **CLOSED with comment** — only when PR is impossible (conflicts, duplicate work), with a clear explanation
### NEVER:
- Run `gh pr merge` — merging is handled externally
- Push directly to main
- Use `git checkout -b` when other agents are active — use worktrees
- Close a PR without a comment explaining why
- Leave PRs open/abandoned — resolve them in the same cycle
- Leave branches or worktrees hanging after merge
- Leave PRs without a self-review comment and `needs-team-review` label
- Leave branches or worktrees hanging after work is done
- Work on a stale base — always `git fetch origin main` before creating a worktree
## Lifecycle Management (MANDATORY — DO NOT EXIT EARLY)
@ -368,23 +389,22 @@ Every PR must reach one of these terminal states:
You MUST remain active until ALL of the following are true:
1. **All tasks are completed**: Run TaskList and confirm every task has status "completed"
2. **All PRs are resolved**: Run `gh pr list --repo OpenRouterTeam/spawn --state open --author @me` and confirm zero open PRs from this cycle. Every PR must be either merged or closed with a comment.
3. **All provider PRs are resolved**: Run `gh pr list --repo OpenRouterTeam/spawn --state open --json number,title,headRefName` and check for ANY open PRs related to cloud providers (branches like `add/*`, `feat/*-cloud`, provider names in titles). For each:
- Check mergeability: `gh pr view NUMBER --json mergeable --jq '.mergeable'`
- If MERGEABLE → merge: `gh pr merge NUMBER --squash --delete-branch`
- If not mergeable → close with comment: `gh pr close NUMBER --comment "Auto-closing: provider PR from interrupted cycle (unmergeable). Please reopen if still needed."`
- **No provider PR should survive across cycles** — resolve every one before exiting
2. **All PRs are self-reviewed and labeled**: Run `gh pr list --repo OpenRouterTeam/spawn --state open --author @me` and confirm every PR from this cycle has a self-review comment and the `needs-team-review` label. Do NOT merge — PRs stay open for external review.
3. **All provider PRs are labeled**: Run `gh pr list --repo OpenRouterTeam/spawn --state open --json number,title,headRefName` and check for ANY open PRs related to cloud providers. For each:
- Ensure it has a self-review comment and `needs-team-review` label
- If not mergeable (conflicts) → close with comment: `gh pr close NUMBER --comment "Auto-closing: provider PR from interrupted cycle (unmergeable). Please reopen if still needed."`
- If mergeable → leave open with label for external review (do NOT merge)
4. **All worktrees are cleaned**: Run `git worktree list` and confirm only the main worktree exists. Run `rm -rf WORKTREE_BASE_PLACEHOLDER` and `git worktree prune`.
5. **All teammates are shut down**: Send `shutdown_request` to EVERY teammate. Wait for each to confirm. Do NOT exit while any teammate is still active.
### Shutdown Sequence (execute in this exact order):
1. Check TaskList — if any tasks are still in_progress or pending, wait and check again (poll every 30 seconds, up to 5 minutes)
2. Verify all PRs merged or closed: `gh pr list --repo OpenRouterTeam/spawn --state open`
2. Verify all PRs are self-reviewed and labeled: `gh pr list --repo OpenRouterTeam/spawn --state open --label "needs-team-review"` (PRs stay open — do NOT merge)
3. **Sweep for leftover provider PRs**: `gh pr list --repo OpenRouterTeam/spawn --state open --json number,title,headRefName,mergeable`
- For each PR whose title or branch references a cloud/provider (e.g. `hetzner`, `vultr`, `runpod`, `add/`, `feat/`):
- MERGEABLE → merge with `gh pr merge NUMBER --squash --delete-branch`
- Not mergeable → close with `gh pr close NUMBER --comment "Auto-closing: stale provider PR. Please reopen if still needed."`
- For each PR whose title or branch references a cloud/provider:
- If mergeable → ensure it has `needs-team-review` label and a self-review comment (do NOT merge)
- If not mergeable → close with `gh pr close NUMBER --comment "Auto-closing: stale provider PR (unmergeable). Please reopen if still needed."`
- Log every action taken
4. For each teammate, send a `shutdown_request` via SendMessage
5. Wait for all `shutdown_response` confirmations
@ -408,8 +428,9 @@ The cycle is NOT complete until this final README update is committed and pushed
- ALWAYS use worktrees — never `git checkout -b` in the main repo
- ALWAYS `git fetch origin main` before creating a worktree
- Each teammate works on DIFFERENT files
- Each unit of work gets its own worktree → branch → PR → merge → cleanup worktree
- **Every PR must be merged OR closed with a comment** — no silent closes, no abandoned PRs
- Each unit of work gets its own worktree → branch → PR → self-review → label → cleanup worktree
- **Every PR must have a self-review comment + `needs-team-review` label, OR be closed with a comment** — no silent closes, no unlabeled PRs
- **NEVER run `gh pr merge`** — merging is handled externally
- Update manifest.json, the cloud's README.md, AND the root README.md matrix
- Clean up worktrees after every PR: `git worktree remove PATH`
- NEVER revert prior macOS/curl-bash compatibility fixes

View file

@ -81,23 +81,21 @@ check_timeout() {
return 0
}
# Robust push → PR → merge with retry on stale main
# Usage: push_and_merge_pr BRANCH_NAME PR_TITLE PR_BODY
push_and_merge_pr() {
# Push → PR → self-review (NO merging — merging is handled externally)
# Usage: push_and_create_pr BRANCH_NAME PR_TITLE PR_BODY
push_and_create_pr() {
local branch_name="$1"
local pr_title="$2"
local pr_body="$3"
local max_retries=3
local attempt=0
# Check there are actual commits to push
if [[ -z "$(git log origin/main..HEAD --oneline 2>/dev/null)" ]]; then
log "push_and_merge_pr: No commits to push on ${branch_name}"
log "push_and_create_pr: No commits to push on ${branch_name}"
return 0
fi
git push -u origin "${branch_name}" 2>&1 | tee -a "${LOG_FILE}" || {
log "push_and_merge_pr: Push failed for ${branch_name}"
log "push_and_create_pr: Push failed for ${branch_name}"
return 1
}
@ -108,36 +106,30 @@ push_and_merge_pr() {
--base main --head "${branch_name}" 2>/dev/null) || true
if [[ -z "${pr_url:-}" ]]; then
log "push_and_merge_pr: PR creation failed for ${branch_name}"
log "push_and_create_pr: PR creation failed for ${branch_name}"
return 1
fi
log "push_and_merge_pr: PR created: ${pr_url}"
log "push_and_create_pr: PR created: ${pr_url}"
while [[ "$attempt" -lt "$max_retries" ]]; do
attempt=$((attempt + 1))
# Extract PR number from URL
local pr_number=""
pr_number=$(printf '%s' "${pr_url}" | grep -oE '[0-9]+$') || true
if gh pr merge "${branch_name}" --squash --delete-branch 2>&1 | tee -a "${LOG_FILE}"; then
log "push_and_merge_pr: Merged ${branch_name} (attempt ${attempt})"
return 0
fi
if [[ -n "${pr_number}" ]]; then
# Self-review: add a comment summarizing the changes
gh pr review "${pr_number}" --repo OpenRouterTeam/spawn --comment \
--body "Self-review by QA cycle: ${pr_title}. Automated change — tests were run before submission." \
2>&1 | tee -a "${LOG_FILE}" || true
log "push_and_merge_pr: Merge failed (attempt ${attempt}/${max_retries}), rebasing onto latest main..."
# Label for external review
gh pr edit "${pr_number}" --repo OpenRouterTeam/spawn --add-label "needs-team-review" \
2>&1 | tee -a "${LOG_FILE}" || true
# Fetch latest main and rebase
git fetch origin main 2>/dev/null || true
if git rebase origin/main 2>&1 | tee -a "${LOG_FILE}"; then
git push --force-with-lease origin "${branch_name}" 2>&1 | tee -a "${LOG_FILE}" || true
sleep 3 # Give GitHub a moment to process
else
git rebase --abort 2>/dev/null || true
log "push_and_merge_pr: Rebase failed for ${branch_name}, giving up"
break
fi
done
log "push_and_create_pr: Self-reviewed and labeled PR #${pr_number} (not merging — awaiting external review)"
fi
log "push_and_merge_pr: Could not merge ${branch_name} after ${max_retries} attempts, leaving PR open"
return 1
return 0
}
# ============================================================
@ -376,7 +368,7 @@ Only modify ${cloud}/lib/common.sh and test/record.sh if the recording infrastru
fi
# Push, PR, and merge with retry on stale main
push_and_merge_pr "${branch_name}" \
push_and_create_pr "${branch_name}" \
"fix: Update ${cloud} API for fixture recording" \
"Automated fix from QA cycle. API recording was failing for ${cloud}." || true
) &
@ -569,7 +561,7 @@ FIXEOF
fi
# Push, PR, and merge with retry on stale main
push_and_merge_pr "${branch_name}" \
push_and_create_pr "${branch_name}" \
"fix: Fix ${cloud} mock test failures" \
"Automated fix from QA cycle. ${fail_count} mock test(s) were failing for ${cloud}: ${failing_scripts}" || true
) &
@ -627,10 +619,10 @@ EOF
)" 2>&1 | tee -a "${LOG_FILE}" || true
# Push, PR, and merge with retry on stale main
push_and_merge_pr "${README_BRANCH}" \
push_and_create_pr "${README_BRANCH}" \
"test: Update README matrix after QA cycle" \
"Automated README update from QA cycle Phase 4. Test results updated in the matrix." || {
log "Phase 4: PR merge failed, leaving PR open for manual review"
log "Phase 4: PR creation failed, check for errors"
}
# Switch back to main and sync

View file

@ -154,7 +154,7 @@ Create these teammates:
- Implement the fix in an isolated worktree
- Run tests to verify the fix
- Create a PR with \`Fixes #${SPAWN_ISSUE}\` in the body
- Merge the PR immediately
- Self-review the PR and label with \`needs-team-review\` (do NOT merge)
2. **issue-tester** (Haiku)
- Review the fix for correctness and edge cases
@ -190,8 +190,10 @@ Track issue lifecycle with labels: "Pending Review" → "Under Review" → "In P
9. When fix is ready:
- Push: \`git push -u origin fix/issue-${SPAWN_ISSUE}\`
- PR: \`gh pr create --title "fix: Description" --body "Fixes #${SPAWN_ISSUE}"\`
- Merge: \`gh pr merge --squash --delete-branch\`
10. Post resolution comment on the issue with PR link
- Self-review: \`gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by issue-fixer: [summary]"\`
- Label: \`gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"\`
- Do NOT merge — PR stays open for external review
10. Post update comment on the issue linking to the PR
11. Remove status labels and close the issue:
\`gh issue edit ${SPAWN_ISSUE} --repo OpenRouterTeam/spawn --remove-label "In Progress"\`
\`gh issue close ${SPAWN_ISSUE}\`
@ -236,6 +238,29 @@ Complexity-hunter: pick the top 1-2 worst functions, fix them, PR, done. Do NOT
Test-engineer: add ONE focused test file, PR, done. Do NOT aim for 100% coverage.
Security-auditor: scan for HIGH/CRITICAL only. Document medium/low, don't fix them.
## No Self-Merge Rule (MANDATORY)
Agents must NEVER merge their own PRs. This applies to ALL agents including the team lead.
### What agents MUST do after creating a PR:
1. **Self-review**: Read the PR diff and add a review comment with findings:
`gh pr diff NUMBER --repo OpenRouterTeam/spawn`
`gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by AGENT-NAME: [summary of changes, what was tested, any concerns]"`
2. **Label for external review**: Add `needs-team-review` label:
`gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"`
3. **Leave the PR open** — do NOT run `gh pr merge`
### What agents must NEVER do:
- `gh pr merge` — NEVER, under any circumstances
- Approve their own PR — self-review is comment-only, not approval
### Why:
- Prevents duplicate or conflicting work from landing unreviewed
- Ensures a second set of eyes (human or another team) validates every change
- Catches planning issues before they hit main
PRs will be reviewed and merged externally (by maintainers or a separate review cycle).
## Team Structure
Create these teammates:
@ -272,13 +297,13 @@ Create these teammates:
- For each remote branch (excluding main):
* Check if there's an open PR: gh pr list --head BRANCH --state open --json number,title
* If open PR exists and branch is stale (last commit >4 hours ago):
- If PR is mergeable: merge it with gh pr merge NUMBER --squash --delete-branch
- If PR has conflicts or failing checks: close it with gh pr close NUMBER --comment "Auto-closing: stale branch with unresolvable conflicts. Please reopen if still needed."
- If PR is mergeable: add a comment noting it's stale and ready for review, ensure it has `needs-team-review` label (do NOT merge — merging is external)
* If no open PR and branch is stale (>4 hours old): delete it with git push origin --delete BRANCH
* If branch is fresh (<4 hours): leave it alone (may be actively worked on)
- After cleanup, report summary: how many branches merged, closed, deleted, left alone
- After cleanup, report summary: how many branches labeled, closed, deleted, left alone
- Run this check AGAIN at the end of the cycle to catch branches created during the cycle
- GOAL: Zero stale branches left on the remote after each cycle.
- GOAL: Zero stale unlabeled branches left on the remote after each cycle.
6. **community-coordinator** (Sonnet)
- FIRST TASK: Run `gh issue list --repo OpenRouterTeam/spawn --state open --json number,title,body,labels,createdAt`
@ -344,17 +369,21 @@ When fixing a bug reported in a GitHub issue:
10. Push the branch: git push -u origin fix/issue-NUMBER
11. Create a PR that references the issue:
gh pr create --title "Fix: description" --body "Fixes #NUMBER"
12. Merge the PR immediately: gh pr merge --squash --delete-branch
12. Self-review and label (DO NOT merge — see No Self-Merge Rule):
gh pr diff NUMBER --repo OpenRouterTeam/spawn
gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by AGENT-NAME: [summary of fix, tests run, confidence level]"
gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"
13. Clean up: git worktree remove WORKTREE_BASE_PLACEHOLDER/fix/issue-NUMBER
14. Community-coordinator posts final resolution comment with PR link and explanation (only if no resolution exists)
15. Remove status labels and close the issue:
gh issue edit NUMBER --repo OpenRouterTeam/spawn --remove-label "In Progress"
gh issue close NUMBER
NEVER leave an issue open after the fix is merged. NEVER leave a PR unmerged.
If a PR cannot be merged (conflicts, superseded, etc.), close it WITH a comment explaining why.
NEVER leave a PR without a self-review comment and `needs-team-review` label.
If a PR cannot be created (conflicts, superseded, etc.), close it WITH a comment explaining why.
NEVER close a PR silently — every closed PR MUST have a comment.
The full cycle is: acknowledge → investigate → worktree → fix → update → PR (references issue) → merge PR → cleanup → resolve & close issue.
The full cycle is: acknowledge → investigate → worktree → fix → update → PR (references issue) → self-review → label → cleanup worktree.
Note: merging is handled externally — agents do NOT merge.
## Commit Markers (MANDATORY)
@ -415,8 +444,12 @@ git push -u origin BRANCH-NAME
# 4. Create PR (can be done from anywhere)
gh pr create --title "title" --body "body"
# 5. Merge and clean up
gh pr merge NUMBER --squash --delete-branch
# 5. Self-review and label (DO NOT merge — see No Self-Merge Rule)
gh pr diff NUMBER --repo OpenRouterTeam/spawn
gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Self-review by AGENT-NAME: [summary]"
gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"
# 6. Clean up worktree (PR stays open for external review)
git worktree remove WORKTREE_BASE_PLACEHOLDER/BRANCH-NAME
```
@ -471,12 +504,13 @@ git worktree remove WORKTREE_BASE_PLACEHOLDER/BRANCH-NAME
2. Immediately start a polling loop — do NOT just output text saying "I'll wait":
while teammates are still active:
a. Run TaskList to check task status
b. Run `gh pr list --repo OpenRouterTeam/spawn --state open` to check for PRs to merge
b. Run `gh pr list --repo OpenRouterTeam/spawn --state open --label "needs-team-review"` to verify PRs have been self-reviewed and labeled
c. When you receive a teammate message, acknowledge it and update task tracking
d. If a teammate reports completion, mark their task done and merge their PR
d. If a teammate reports completion, mark their task done — verify their PR has a self-review comment and label
e. If a teammate reports an error, coordinate resolution
f. If the time budget is almost up, send wrap-up messages to all teammates
g. If no messages received yet, run `Bash("sleep 30")` then loop back to (a)
h. REMINDER: Do NOT merge any PRs — they stay open for external review
3. Only after ALL teammates have finished, proceed to shutdown
```
@ -491,7 +525,7 @@ GOOD: Spawn teammates → TaskList → sleep 30 → TaskList → receive message
You MUST remain active until ALL of the following are true:
1. **All tasks are completed**: Run TaskList and confirm every task has status "completed"
2. **All PRs are resolved**: Run `gh pr list --repo OpenRouterTeam/spawn --state open --author @me` and confirm zero open PRs from this cycle. Every PR must be either merged or closed with a comment.
2. **All PRs are self-reviewed and labeled**: Run `gh pr list --repo OpenRouterTeam/spawn --state open --label "needs-team-review"` and confirm every PR from this cycle has a self-review comment and the `needs-team-review` label. Do NOT merge — PRs stay open for external review.
3. **All issues are engaged and labeled**: Run `gh issue list --repo OpenRouterTeam/spawn --state open --json number,labels`
and for EACH open issue, verify it has at least one comment AND has a status label
("Pending Review", "Under Review", or "In Progress"). If any issue is missing a status
@ -503,7 +537,7 @@ You MUST remain active until ALL of the following are true:
### Shutdown Sequence (execute in this exact order):
1. Check TaskList — if any tasks are still in_progress or pending, wait and check again (poll every 30 seconds, up to 10 minutes)
2. Verify all PRs merged or closed: `gh pr list --repo OpenRouterTeam/spawn --state open`
2. Verify all PRs are self-reviewed and labeled: `gh pr list --repo OpenRouterTeam/spawn --state open --label "needs-team-review"` (PRs stay open — do NOT merge)
3. Verify all issues engaged: `gh issue list --repo OpenRouterTeam/spawn --state open`
4. For each teammate, send a `shutdown_request` via SendMessage
5. Wait for all `shutdown_response` confirmations