mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-19 08:01:17 +00:00
feat: consolidate security modes — merge pr+hygiene into review_all (#739)
Simplify from 6 modes (Hexa-Mode) to 4 modes (Quad-Mode) by folding single-PR review and hygiene into a unified review_all mode that runs every 15 minutes. This removes the pull_request trigger entirely since review_all catches all open PRs on schedule, and absorbs staleness checks + branch cleanup into the same cycle. Remaining modes: team_building, triage, review_all, scan. Co-authored-by: Sprite <noreply@sprites.dev> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4924a7d5db
commit
15e2ca6caf
2 changed files with 182 additions and 265 deletions
|
|
@ -1,13 +1,12 @@
|
|||
#!/bin/bash
|
||||
set -eo pipefail
|
||||
|
||||
# Security Review Team Service — Single Cycle (Penta-Mode)
|
||||
# Security Review Team Service — Single Cycle (Quad-Mode)
|
||||
# Triggered by trigger-server.ts via GitHub Actions
|
||||
#
|
||||
# RUN_MODE=team_building — implement team changes from issue (reason=team_building, 15 min)
|
||||
# RUN_MODE=triage — single-agent issue triage for prompt injection/spam (reason=triage, 5 min)
|
||||
# RUN_MODE=pr — 2-agent security review for a specific PR (10 min)
|
||||
# RUN_MODE=hygiene — stale PR cleanup + triage (reason=hygiene, 15 min)
|
||||
# RUN_MODE=review_all — batch security review + hygiene for ALL open PRs (reason=review_all, 30 min)
|
||||
# RUN_MODE=scan — full repo security scan + issue filing (reason=schedule, 20 min)
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
|
@ -37,17 +36,11 @@ elif [[ "${SPAWN_REASON}" == "triage" ]] && [[ -n "${SPAWN_ISSUE}" ]]; then
|
|||
WORKTREE_BASE="/tmp/spawn-worktrees/triage-${ISSUE_NUM}"
|
||||
TEAM_NAME="spawn-triage-${ISSUE_NUM}"
|
||||
CYCLE_TIMEOUT=300 # 5 min for issue triage
|
||||
elif [[ -n "${SPAWN_ISSUE}" ]]; then
|
||||
RUN_MODE="pr"
|
||||
PR_NUM="${SPAWN_ISSUE}"
|
||||
WORKTREE_BASE="/tmp/spawn-worktrees/security-pr-${PR_NUM}"
|
||||
TEAM_NAME="spawn-security-pr-${PR_NUM}"
|
||||
CYCLE_TIMEOUT=600 # 10 min for PR reviews
|
||||
elif [[ "${SPAWN_REASON}" == "hygiene" ]]; then
|
||||
RUN_MODE="hygiene"
|
||||
WORKTREE_BASE="/tmp/spawn-worktrees/security-hygiene"
|
||||
TEAM_NAME="spawn-security-hygiene"
|
||||
CYCLE_TIMEOUT=900 # 15 min for PR hygiene
|
||||
elif [[ "${SPAWN_REASON}" == "review_all" ]]; then
|
||||
RUN_MODE="review_all"
|
||||
WORKTREE_BASE="/tmp/spawn-worktrees/security-review-all"
|
||||
TEAM_NAME="spawn-security-review-all"
|
||||
CYCLE_TIMEOUT=1800 # 30 min for batch review
|
||||
else
|
||||
RUN_MODE="scan"
|
||||
WORKTREE_BASE="/tmp/spawn-worktrees/security-scan"
|
||||
|
|
@ -95,9 +88,7 @@ log "Working directory: ${REPO_ROOT}"
|
|||
log "Team name: ${TEAM_NAME}"
|
||||
log "Worktree base: ${WORKTREE_BASE}"
|
||||
log "Timeout: ${CYCLE_TIMEOUT}s"
|
||||
if [[ "${RUN_MODE}" == "pr" ]]; then
|
||||
log "PR: #${PR_NUM}"
|
||||
elif [[ "${RUN_MODE}" == "team_building" ]] || [[ "${RUN_MODE}" == "triage" ]]; then
|
||||
if [[ "${RUN_MODE}" == "team_building" ]] || [[ "${RUN_MODE}" == "triage" ]]; then
|
||||
log "Issue: #${ISSUE_NUM}"
|
||||
fi
|
||||
|
||||
|
|
@ -296,131 +287,171 @@ fi
|
|||
Begin now. Triage issue #${ISSUE_NUM}.
|
||||
TRIAGE_PROMPT_EOF
|
||||
|
||||
elif [[ "${RUN_MODE}" == "pr" ]]; then
|
||||
# --- PR mode: 2-agent security review ---
|
||||
cat > "${PROMPT_FILE}" << PR_PROMPT_EOF
|
||||
You are the Team Lead for a security review of PR #${PR_NUM} on the spawn codebase.
|
||||
elif [[ "${RUN_MODE}" == "review_all" ]]; then
|
||||
# --- Review-all mode: batch security review + hygiene for ALL open PRs ---
|
||||
cat > "${PROMPT_FILE}" << REVIEW_ALL_PROMPT_EOF
|
||||
You are the Team Lead for a batch security review and hygiene cycle on the spawn codebase.
|
||||
|
||||
## Target PR
|
||||
## Mission
|
||||
|
||||
Review PR #${PR_NUM} for security issues.
|
||||
|
||||
First, fetch the PR details:
|
||||
\`\`\`bash
|
||||
gh pr view ${PR_NUM} --repo OpenRouterTeam/spawn
|
||||
gh pr diff ${PR_NUM} --repo OpenRouterTeam/spawn
|
||||
gh pr view ${PR_NUM} --repo OpenRouterTeam/spawn --json files --jq '.files[].path'
|
||||
\`\`\`
|
||||
List every open PR and run the full security review checklist on each one. Approve+merge clean PRs, request changes on flagged ones. Close stale PRs. Clean up orphan branches.
|
||||
|
||||
## Time Budget
|
||||
|
||||
This cycle MUST complete within 8 minutes. This is a HARD deadline.
|
||||
This cycle MUST complete within 25 minutes. This is a HARD deadline.
|
||||
|
||||
- At the 5-minute mark, stop new work and wrap up
|
||||
- At the 7-minute mark, send shutdown_request to all agents
|
||||
- At 8 minutes, force shutdown
|
||||
- At the 20-minute mark, stop spawning new reviewers and wrap up
|
||||
- At the 24-minute mark, send shutdown_request to all agents
|
||||
- At 25 minutes, force shutdown
|
||||
|
||||
## Team Structure
|
||||
## Step 1 — Discover Open PRs
|
||||
|
||||
Create these teammates:
|
||||
|
||||
1. **code-reviewer** (Opus)
|
||||
- Fetch the full PR diff: \`gh pr diff ${PR_NUM} --repo OpenRouterTeam/spawn\`
|
||||
- Review every changed file for security issues:
|
||||
* **Command injection**: unquoted variables in shell commands, unsafe eval/heredoc, unsanitized input in bash
|
||||
* **Credential leaks**: hardcoded API keys, tokens, passwords; secrets logged to stdout; credentials in committed files
|
||||
* **Path traversal**: unsanitized file paths, directory escape via ../
|
||||
* **XSS/injection**: unsafe HTML rendering, prototype pollution, SQL injection, template injection
|
||||
* **Unsafe patterns**: use of \`eval\`, \`source <()\`, unvalidated redirects, TOCTOU races
|
||||
* **curl|bash safety**: broken source/eval fallback patterns, missing integrity checks
|
||||
* **macOS bash 3.x compat**: echo -e, source <(), ((var++)) with set -e, local in subshells, set -u
|
||||
- Classify each finding as CRITICAL, HIGH, MEDIUM, or LOW
|
||||
- Report findings to the team lead with file paths and line numbers
|
||||
|
||||
2. **test-verifier** (Haiku)
|
||||
- Get the list of changed files: \`gh pr view ${PR_NUM} --repo OpenRouterTeam/spawn --json files --jq '.files[].path'\`
|
||||
- For each changed .sh file:
|
||||
* Run \`bash -n FILE\` to check syntax
|
||||
* Verify it starts with \`#!/bin/bash\` and \`set -eo pipefail\`
|
||||
* Verify the local-or-remote source fallback pattern is used (not bare \`source ./lib/...\`)
|
||||
* Check for macOS bash 3.x incompatibilities (echo -e, source <(), etc.)
|
||||
- For changed .ts files:
|
||||
* Run \`bun test\` to verify tests pass
|
||||
- Report results to the team lead
|
||||
|
||||
## Review Decision
|
||||
|
||||
After both agents report back, make the final decision:
|
||||
|
||||
### If CRITICAL or HIGH issues found:
|
||||
1. Post a **requesting-changes** review on the PR:
|
||||
\`\`\`bash
|
||||
gh pr review ${PR_NUM} --repo OpenRouterTeam/spawn --request-changes --body "REVIEW_BODY"
|
||||
\`\`\`
|
||||
Include all CRITICAL/HIGH findings with file paths, line numbers, and remediation suggestions.
|
||||
|
||||
2. If SLACK_WEBHOOK is set, send a Slack notification:
|
||||
\`\`\`bash
|
||||
PR_TITLE=\$(gh pr view ${PR_NUM} --repo OpenRouterTeam/spawn --json title --jq '.title')
|
||||
PR_URL="https://github.com/OpenRouterTeam/spawn/pull/${PR_NUM}"
|
||||
curl -s -X POST "\${SLACK_WEBHOOK}" -H 'Content-Type: application/json' \\
|
||||
-d "{\"text\":\":warning: Security concern on PR #${PR_NUM}: \${PR_TITLE} — [summary of top concern] — \${PR_URL}\"}"
|
||||
\`\`\`
|
||||
(The SLACK_WEBHOOK env var is: ${SLACK_WEBHOOK:-NOT_SET})
|
||||
|
||||
### If only MEDIUM/LOW issues or no issues:
|
||||
1. Post an **approving** review on the PR:
|
||||
\`\`\`bash
|
||||
gh pr review ${PR_NUM} --repo OpenRouterTeam/spawn --approve --body "REVIEW_BODY"
|
||||
\`\`\`
|
||||
Include any MEDIUM/LOW findings as informational notes.
|
||||
|
||||
2. **Merge the PR** (squash merge, delete branch):
|
||||
\`\`\`bash
|
||||
gh pr merge ${PR_NUM} --repo OpenRouterTeam/spawn --squash --delete-branch
|
||||
\`\`\`
|
||||
Merge if ALL of these are true:
|
||||
- Zero CRITICAL or HIGH findings from code-reviewer
|
||||
- All bash -n checks pass
|
||||
- All bun tests pass (or N/A)
|
||||
If merge fails (e.g. conflicts, branch protection), log the error and move on.
|
||||
|
||||
### Review body format:
|
||||
Run:
|
||||
\`\`\`bash
|
||||
gh pr list --repo OpenRouterTeam/spawn --state open --json number,title,headRefName,updatedAt,mergeable
|
||||
\`\`\`
|
||||
## Security Review
|
||||
|
||||
**Verdict**: [APPROVED / CHANGES REQUESTED]
|
||||
If zero open PRs, skip to Step 3 (branch cleanup) — do NOT exit yet.
|
||||
|
||||
### Findings
|
||||
- [SEVERITY] file:line — description
|
||||
## Step 2 — Create the Team and Spawn Reviewers
|
||||
|
||||
### Tests
|
||||
- bash -n: [PASS/FAIL]
|
||||
- bun test: [PASS/FAIL/N/A]
|
||||
- curl|bash pattern: [OK/MISSING]
|
||||
- macOS compat: [OK/ISSUES]
|
||||
1. Create the team with TeamCreate (team_name="${TEAM_NAME}")
|
||||
2. For EACH open PR, create a task with TaskCreate describing the review work
|
||||
3. Spawn a **pr-reviewer** agent (model=opus) for each PR using the Task tool (subagent_type='general-purpose', team_name="${TEAM_NAME}")
|
||||
- Name each agent: pr-reviewer-NUMBER (e.g. pr-reviewer-42)
|
||||
- Each agent gets instructions below
|
||||
4. Also spawn a **branch-cleaner** agent (model=haiku) — see Step 3
|
||||
|
||||
---
|
||||
*Automated security review by spawn security team*
|
||||
### Per-PR Reviewer Instructions
|
||||
|
||||
Each pr-reviewer agent must:
|
||||
|
||||
1. Fetch the PR metadata and diff:
|
||||
\`\`\`bash
|
||||
gh pr view NUMBER --repo OpenRouterTeam/spawn --json updatedAt,mergeable,title,headRefName
|
||||
gh pr diff NUMBER --repo OpenRouterTeam/spawn
|
||||
gh pr view NUMBER --repo OpenRouterTeam/spawn --json files --jq '.files[].path'
|
||||
\`\`\`
|
||||
|
||||
2. **Staleness check first** — Before doing security review, check:
|
||||
* Is \`updatedAt\` > 48 hours ago AND \`mergeable\` is \`CONFLICTING\`?
|
||||
- YES → **Close the PR** (stale + conflicts):
|
||||
\`\`\`bash
|
||||
gh pr close NUMBER --repo OpenRouterTeam/spawn --comment "Auto-closing: this PR has been stale for >48h with merge conflicts. Please reopen or create a fresh PR if still needed."
|
||||
\`\`\`
|
||||
Then delete the branch:
|
||||
\`\`\`bash
|
||||
BRANCH=\$(gh pr view NUMBER --repo OpenRouterTeam/spawn --json headRefName --jq '.headRefName')
|
||||
git push origin --delete "\${BRANCH}" 2>/dev/null || true
|
||||
\`\`\`
|
||||
Report to team lead: "PR #NUMBER closed (stale+conflicts)" and STOP — skip security review.
|
||||
* Is \`updatedAt\` > 48 hours ago but NO conflicts?
|
||||
- The PR is stale but mergeable — still do the security review below (it may be fine to merge).
|
||||
* Is the PR fresh (<48h)? → Proceed to security review normally.
|
||||
|
||||
3. Review every changed file for security issues:
|
||||
* **Command injection**: unquoted variables in shell commands, unsafe eval/heredoc, unsanitized input in bash
|
||||
* **Credential leaks**: hardcoded API keys, tokens, passwords; secrets logged to stdout; credentials in committed files
|
||||
* **Path traversal**: unsanitized file paths, directory escape via ../
|
||||
* **XSS/injection**: unsafe HTML rendering, prototype pollution, SQL injection, template injection
|
||||
* **Unsafe patterns**: use of \`eval\`, \`source <()\`, unvalidated redirects, TOCTOU races
|
||||
* **curl|bash safety**: broken source/eval fallback patterns, missing integrity checks
|
||||
* **macOS bash 3.x compat**: echo -e, source <(), ((var++)) with set -e, local in subshells, set -u
|
||||
|
||||
4. For each changed .sh file:
|
||||
* Run \`bash -n FILE\` to check syntax
|
||||
* Verify the local-or-remote source fallback pattern is used
|
||||
* Check for macOS bash 3.x incompatibilities
|
||||
|
||||
5. For changed .ts files:
|
||||
* Run \`bun test\` to verify tests pass
|
||||
|
||||
6. Classify each finding as CRITICAL, HIGH, MEDIUM, or LOW
|
||||
|
||||
7. Make the review decision:
|
||||
|
||||
**If CRITICAL or HIGH issues found** — request changes:
|
||||
\`\`\`bash
|
||||
gh pr review NUMBER --repo OpenRouterTeam/spawn --request-changes --body "REVIEW_BODY"
|
||||
\`\`\`
|
||||
|
||||
**If only MEDIUM/LOW or no issues** — approve AND merge:
|
||||
\`\`\`bash
|
||||
gh pr review NUMBER --repo OpenRouterTeam/spawn --approve --body "REVIEW_BODY"
|
||||
gh pr merge NUMBER --repo OpenRouterTeam/spawn --squash --delete-branch
|
||||
\`\`\`
|
||||
If merge fails (conflicts, branch protection), log the error and move on.
|
||||
|
||||
8. Review body format:
|
||||
\`\`\`
|
||||
## Security Review
|
||||
|
||||
**Verdict**: [APPROVED / CHANGES REQUESTED]
|
||||
|
||||
### Findings
|
||||
- [SEVERITY] file:line — description
|
||||
|
||||
### Tests
|
||||
- bash -n: [PASS/FAIL]
|
||||
- bun test: [PASS/FAIL/N/A]
|
||||
- curl|bash pattern: [OK/MISSING]
|
||||
- macOS compat: [OK/ISSUES]
|
||||
|
||||
---
|
||||
*Automated security review by spawn security team*
|
||||
\`\`\`
|
||||
|
||||
9. Report results to the team lead: PR number, verdict (approved+merged / changes-requested / closed-stale), finding count, merge status
|
||||
|
||||
## Step 3 — Branch Cleanup
|
||||
|
||||
Spawn a **branch-cleaner** agent (model=haiku, team_name="${TEAM_NAME}", name="branch-cleaner"):
|
||||
|
||||
- List all remote branches: \`git branch -r --format='%(refname:short) %(committerdate:unix)'\`
|
||||
- For each branch (excluding main):
|
||||
* Check if there's an open PR: \`gh pr list --head BRANCH --state open --json number\`
|
||||
* If NO open PR and branch is stale (>48 hours): delete it \`git push origin --delete BRANCH\`
|
||||
* If open PR exists: leave it (pr-reviewers handle PRs)
|
||||
- Report summary: how many branches deleted, how many left
|
||||
|
||||
## Step 4 — Monitor and Collect Results
|
||||
|
||||
Poll TaskList every 15 seconds. As each agent reports back, record:
|
||||
- PR number
|
||||
- Verdict (approved+merged / changes-requested / closed-stale)
|
||||
- Number of findings by severity
|
||||
- Branches deleted (from branch-cleaner)
|
||||
|
||||
## Step 5 — Summary and Slack Notification
|
||||
|
||||
After all agents finish (or time runs out), compile the summary.
|
||||
|
||||
If SLACK_WEBHOOK is set, send a Slack notification:
|
||||
\`\`\`bash
|
||||
SLACK_WEBHOOK="${SLACK_WEBHOOK:-NOT_SET}"
|
||||
if [ -n "\${SLACK_WEBHOOK}" ] && [ "\${SLACK_WEBHOOK}" != "NOT_SET" ]; then
|
||||
curl -s -X POST "\${SLACK_WEBHOOK}" -H 'Content-Type: application/json' \\
|
||||
-d '{"text":":shield: PR review+hygiene complete: N PRs reviewed (X merged, Y flagged, Z closed-stale), K branches cleaned. See https://github.com/OpenRouterTeam/spawn/pulls"}'
|
||||
fi
|
||||
\`\`\`
|
||||
(The SLACK_WEBHOOK env var is: ${SLACK_WEBHOOK:-NOT_SET})
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Create the team with TeamCreate (team_name="${TEAM_NAME}")
|
||||
2. Create tasks with TaskCreate for each teammate's work
|
||||
3. Fetch PR details and diff
|
||||
4. Spawn teammates in parallel using Task tool (subagent_type='general-purpose', team_name="${TEAM_NAME}"):
|
||||
- code-reviewer (model=opus): security review of the diff
|
||||
- test-verifier (model=haiku): syntax/test verification
|
||||
1. List open PRs: \`gh pr list --state open --json number,title,headRefName,updatedAt,mergeable\`
|
||||
2. Create the team with TeamCreate (team_name="${TEAM_NAME}")
|
||||
3. Spawn branch-cleaner agent (model=haiku)
|
||||
4. For each PR:
|
||||
a. Create a task with TaskCreate
|
||||
b. Spawn a pr-reviewer agent (model=opus, team_name="${TEAM_NAME}", name="pr-reviewer-NUMBER")
|
||||
5. Assign tasks to teammates using TaskUpdate (set owner to teammate name)
|
||||
6. Monitor teammates (poll TaskList, sleep 15 between checks)
|
||||
7. Collect results from both agents via messages
|
||||
8. Make the review decision:
|
||||
- If CRITICAL/HIGH → request changes + Slack notification
|
||||
- If MEDIUM/LOW or clean → approve AND merge (squash + delete branch)
|
||||
9. Shutdown all teammates via SendMessage (type=shutdown_request)
|
||||
10. Clean up with TeamDelete
|
||||
11. Exit
|
||||
7. Collect results from all agents via messages
|
||||
8. Compile summary (N reviewed, X merged, Y flagged, Z closed-stale, K branches cleaned)
|
||||
9. Send Slack notification
|
||||
10. Shutdown all teammates via SendMessage (type=shutdown_request)
|
||||
11. Clean up with TeamDelete
|
||||
12. Exit
|
||||
|
||||
## CRITICAL: Monitoring Loop
|
||||
|
||||
|
|
@ -432,127 +463,24 @@ Required pattern:
|
|||
a. Run TaskList to check status
|
||||
b. If messages received, process them
|
||||
c. If no messages yet, run Bash("sleep 15") then loop back
|
||||
3. Once both agents report, make review decision
|
||||
4. Post review and merge if no CRITICAL/HIGH findings
|
||||
5. Send Slack notification if concerns found
|
||||
6. Shutdown teammates and exit
|
||||
3. Once all agents report (or time is up), compile summary
|
||||
4. Send Slack notification
|
||||
5. Shutdown teammates and exit
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- NEVER approve a PR with CRITICAL or HIGH findings
|
||||
- Auto-merge PRs that have no CRITICAL/HIGH findings and all tests pass
|
||||
- MEDIUM/LOW findings are informational — still approve and merge
|
||||
- NEVER close a PR without posting a comment explaining why
|
||||
- If a PR has recent activity (<24h), never close it for staleness
|
||||
- If unsure about a finding, flag it as MEDIUM and note the uncertainty
|
||||
- Always include file paths and line numbers in findings
|
||||
- Do not modify any code — this is review only
|
||||
- Limit to at most 10 concurrent reviewer agents to avoid API rate limits
|
||||
|
||||
Begin now. Review PR #${PR_NUM}.
|
||||
PR_PROMPT_EOF
|
||||
|
||||
elif [[ "${RUN_MODE}" == "hygiene" ]]; then
|
||||
# --- Hygiene mode: stale PR cleanup + triage ---
|
||||
cat > "${PROMPT_FILE}" << 'HYGIENE_PROMPT_EOF'
|
||||
You are the Team Lead for a PR hygiene cycle on the spawn codebase.
|
||||
|
||||
## Mission
|
||||
|
||||
Go through ALL open PRs. For each one: review it, decide whether to close or keep, and take action. Also file issues for anything that needs follow-up.
|
||||
|
||||
## Time Budget
|
||||
|
||||
This cycle MUST complete within 12 minutes. This is a HARD deadline.
|
||||
|
||||
- At the 9-minute mark, stop new work and wrap up
|
||||
- At the 11-minute mark, send shutdown_request to all agents
|
||||
- At 12 minutes, force shutdown
|
||||
|
||||
## Team Structure
|
||||
|
||||
Create these teammates:
|
||||
|
||||
1. **pr-triager** (Opus)
|
||||
- List ALL open PRs: `gh pr list --repo OpenRouterTeam/spawn --state open --json number,title,updatedAt,author,mergeable,headRefName,labels`
|
||||
- For EACH open PR, evaluate:
|
||||
* **Staleness**: last updated > 48 hours ago? (use `updatedAt` field)
|
||||
* **Mergeable**: does it have merge conflicts? (`mergeable` field)
|
||||
* **CI status**: `gh pr checks NUMBER --repo OpenRouterTeam/spawn` — are checks passing?
|
||||
* **Review status**: does it have reviews? `gh pr view NUMBER --repo OpenRouterTeam/spawn --json reviews --jq '.reviews[].state'`
|
||||
* **Relevance**: read the diff (`gh pr diff NUMBER --repo OpenRouterTeam/spawn`) — is the change still relevant to the current codebase?
|
||||
- For each PR, take ONE of these actions:
|
||||
* **Close** (stale + conflicts + no activity):
|
||||
`gh pr close NUMBER --repo OpenRouterTeam/spawn --comment "Auto-closing: this PR has been stale for >48h with merge conflicts. The changes may no longer be relevant. Please reopen or create a fresh PR if still needed."`
|
||||
Then delete the branch: `gh pr view NUMBER --repo OpenRouterTeam/spawn --json headRefName --jq '.headRefName'` → `git push origin --delete BRANCH`
|
||||
* **Close with issue** (stale but has good ideas):
|
||||
Close the PR with a comment, then file a new issue capturing the intent:
|
||||
`gh issue create --repo OpenRouterTeam/spawn --title "Follow-up: [original PR intent]" --body "Original PR #NUMBER was auto-closed due to staleness. The approach had merit: [summary]. Needs a fresh implementation." --label "enhancement"`
|
||||
* **Request review** (looks good but needs eyes):
|
||||
`gh pr review NUMBER --repo OpenRouterTeam/spawn --comment --body "Automated triage: This PR looks viable but needs human review. [summary of what it does and any concerns]"`
|
||||
Add label: `gh pr edit NUMBER --repo OpenRouterTeam/spawn --add-label "needs-team-review"`
|
||||
* **Leave alone** (fresh, actively worked on): skip it
|
||||
- Report all actions taken to the team lead
|
||||
|
||||
2. **branch-cleaner** (Haiku)
|
||||
- List all remote branches: `git branch -r --format='%(refname:short) %(committerdate:unix)'`
|
||||
- For each branch (excluding main):
|
||||
* Check if there's an open PR: `gh pr list --head BRANCH --state open --json number`
|
||||
* If NO open PR and branch is stale (>48 hours): delete it `git push origin --delete BRANCH`
|
||||
* If open PR exists: leave it (pr-triager handles PRs)
|
||||
- Report summary: how many branches deleted, how many left
|
||||
|
||||
## Actions Summary
|
||||
|
||||
After both agents report, compile a summary:
|
||||
|
||||
### If any PRs were closed or issues filed, send a Slack notification:
|
||||
```bash
|
||||
SLACK_WEBHOOK="${SLACK_WEBHOOK:-}"
|
||||
if [ -n "$SLACK_WEBHOOK" ]; then
|
||||
curl -s -X POST "$SLACK_WEBHOOK" -H 'Content-Type: application/json' \
|
||||
-d '{"text":":broom: PR Hygiene cycle complete: [N PRs closed, M issues filed, K branches deleted]. See details in the workflow run."}'
|
||||
fi
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Create the team with TeamCreate (team_name="spawn-security-hygiene")
|
||||
2. Create tasks with TaskCreate for each teammate's work
|
||||
3. Spawn teammates in parallel using Task tool (subagent_type='general-purpose', team_name="spawn-security-hygiene"):
|
||||
- pr-triager (model=opus): review and triage all open PRs
|
||||
- branch-cleaner (model=haiku): clean up stale orphan branches
|
||||
4. Assign tasks to teammates using TaskUpdate (set owner to teammate name)
|
||||
5. Monitor teammates (poll TaskList, sleep 15 between checks)
|
||||
6. Collect results from both agents via messages
|
||||
7. Compile summary and send Slack notification
|
||||
8. Shutdown all teammates via SendMessage (type=shutdown_request)
|
||||
9. Clean up with TeamDelete
|
||||
10. Exit
|
||||
|
||||
## CRITICAL: Monitoring Loop
|
||||
|
||||
**Spawning teammates is the BEGINNING of your job, not the end.** After spawning all teammates, you MUST actively monitor them. Your session ENDS the moment you produce a response with no tool call. To stay alive, you MUST ALWAYS include at least one tool call in every response.
|
||||
|
||||
Required pattern:
|
||||
1. Spawn teammates via Task tool (with team_name and name params)
|
||||
2. Poll loop:
|
||||
a. Run TaskList to check status
|
||||
b. If messages received, process them
|
||||
c. If no messages yet, run Bash("sleep 15") then loop back
|
||||
3. Once both agents report, compile summary
|
||||
4. Send Slack notification if actions were taken
|
||||
5. Shutdown teammates and exit
|
||||
|
||||
## Safety Rules
|
||||
|
||||
- NEVER close a PR without posting a comment explaining why
|
||||
- If a PR has recent activity (<24h), leave it alone regardless of other factors
|
||||
- When filing follow-up issues, include the original PR number and a clear description
|
||||
- Do not modify any code — this is triage only
|
||||
|
||||
Begin now. Start the PR hygiene cycle.
|
||||
HYGIENE_PROMPT_EOF
|
||||
|
||||
# Substitute SLACK_WEBHOOK in the hygiene prompt (it uses double-quote heredoc workaround)
|
||||
sed -i "s|\${SLACK_WEBHOOK:-}|${SLACK_WEBHOOK:-}|g" "${PROMPT_FILE}"
|
||||
Begin now. Review all open PRs and clean up stale branches.
|
||||
REVIEW_ALL_PROMPT_EOF
|
||||
|
||||
else
|
||||
# --- Scan mode: full repo security audit + issue filing ---
|
||||
|
|
|
|||
51
.github/workflows/security.yml
vendored
51
.github/workflows/security.yml
vendored
|
|
@ -1,39 +1,32 @@
|
|||
name: Security Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
schedule:
|
||||
# Full repo security scan — daily at 06:00 UTC
|
||||
- cron: '0 6 * * *'
|
||||
# PR hygiene (stale PR cleanup) — every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
# Batch PR security review + hygiene — every 15 min
|
||||
- cron: '*/15 * * * *'
|
||||
# Full repo security scan — every 30 min (offset +5)
|
||||
- cron: '5,35 * * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
mode:
|
||||
description: 'Run mode: pr (needs PR number), hygiene, or scan'
|
||||
description: 'Run mode: review_all (PR review + hygiene) or scan (repo audit)'
|
||||
required: false
|
||||
default: 'scan'
|
||||
default: 'review_all'
|
||||
type: choice
|
||||
options:
|
||||
- review_all
|
||||
- scan
|
||||
- hygiene
|
||||
pr_number:
|
||||
description: 'PR number (only for pr mode via workflow_dispatch)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: security-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.event_name == 'issues' && format('issue-{0}', github.event.issue.number) || github.event_name == 'schedule' && github.event.schedule || 'manual' }}
|
||||
group: security-${{ github.event_name == 'issues' && format('issue-{0}', github.event.issue.number) || github.event_name == 'schedule' && github.event.schedule || 'manual' }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
review:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
# Trigger on ALL issues (triage or team-building) plus PR/schedule/manual
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: Trigger security review
|
||||
env:
|
||||
|
|
@ -45,11 +38,8 @@ jobs:
|
|||
exit 0
|
||||
fi
|
||||
|
||||
# Determine reason and issue/PR number based on trigger type
|
||||
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
||||
REASON="pull_request"
|
||||
ISSUE_NUM="${{ github.event.pull_request.number }}"
|
||||
elif [ "${{ github.event_name }}" = "issues" ]; then
|
||||
# Determine reason and issue number based on trigger type
|
||||
if [ "${{ github.event_name }}" = "issues" ]; then
|
||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||
if [ "${{ contains(github.event.issue.labels.*.name, 'team-building') }}" = "true" ]; then
|
||||
REASON="team_building"
|
||||
|
|
@ -57,25 +47,24 @@ jobs:
|
|||
REASON="triage"
|
||||
fi
|
||||
elif [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
# Distinguish between cron schedules:
|
||||
# '0 6 * * *' = daily scan, '0 */6 * * *' = hygiene every 6h
|
||||
# Distinguish between cron schedules by their cron string
|
||||
CRON="${{ github.event.schedule }}"
|
||||
if [ "$CRON" = "0 6 * * *" ]; then
|
||||
if [ "$CRON" = "*/15 * * * *" ]; then
|
||||
REASON="review_all"
|
||||
elif [ "$CRON" = "5,35 * * * *" ]; then
|
||||
REASON="schedule"
|
||||
else
|
||||
REASON="hygiene"
|
||||
REASON="schedule"
|
||||
fi
|
||||
ISSUE_NUM=""
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
MODE="${{ github.event.inputs.mode || 'scan' }}"
|
||||
ISSUE_NUM="${{ github.event.inputs.pr_number || '' }}"
|
||||
if [ -n "$ISSUE_NUM" ]; then
|
||||
REASON="pull_request"
|
||||
elif [ "$MODE" = "hygiene" ]; then
|
||||
REASON="hygiene"
|
||||
MODE="${{ github.event.inputs.mode || 'review_all' }}"
|
||||
if [ "$MODE" = "review_all" ]; then
|
||||
REASON="review_all"
|
||||
else
|
||||
REASON="schedule"
|
||||
fi
|
||||
ISSUE_NUM=""
|
||||
else
|
||||
REASON="schedule"
|
||||
ISSUE_NUM=""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue