pr-triage
First-touch triage for external pull requests - verdict, label, and a welcoming comment within minutes of open
git clone --depth 1 https://github.com/aeonfun/aeon /tmp/pr-triage && cp -r /tmp/pr-triage/skills/pr-triage ~/.claude/skills/pr-triageSKILL.md
> **${var}** — PR scope. Accepts `owner/repo`, `owner/repo#N`, or empty (all watched repos). If empty, scans every repo in `memory/watched-repos.md`.
External PRs that sit unanswered look unwelcoming. This skill is the **first touch** for every external pull request — it reads the diff, applies a structured rubric, posts a comment with a verdict + rationale, and labels the PR so a human reviewer can pick it up with full context. It is not a substitute for `pr-review` (depth) or `auto-merge` (execution); it is the welcoming layer that runs before either of those decide whether to engage.
## What "external" means
Any PR whose author is **not** in the trusted set qualifies. The trusted set is, by precedence:
1. Logins ending in `[bot]` (`dependabot[bot]`, `renovate[bot]`, `github-actions[bot]`, …) — these route to `auto-merge` / `pr-review`.
2. The agent's own login: `aeonframework`, `aeonfun`, and any login under a `## Trusted Authors` heading in `memory/watched-repos.md` (same allowlist convention used by `auto-merge`).
Everything else is **external** and gets triaged.
## Config
`memory/watched-repos.md`:
```markdown
# Watched Repos
- aeonfun/aeon
- aaronjmars/aeon-agent
## Trusted Authors
- alice
- bob
```
If the file is missing and `${var}` is empty, log `PR_TRIAGE_OK no-watched-repos` and exit.
---
Read `memory/MEMORY.md` for context.
Read `memory/triaged-prs.json` (if present; else treat as `{}`) — schema: `{"owner/repo": [{"n": 143, "sha": "abc123", "at": "2026-04-29", "verdict": "ACCEPTED"}, ...]}`. Used for idempotency keyed on (PR number, headRefOid).
Read the last 2 days of `memory/logs/` as a fallback dedup signal in case the JSON state file is missing.
## Steps
### 1. Resolve targets
- `${var}` matches `^[\w.-]+/[\w.-]+#\d+$` → single-PR mode: target that exact PR.
- `${var}` matches `^[\w.-]+/[\w.-]+$` → repo mode: scan all open PRs on that one repo.
- `${var}` is set but matches neither shape → abort with `pr-triage: invalid var — expected owner/repo or owner/repo#N` and exit.
- `${var}` is empty → fleet mode: read non-comment, non-blank `- ` lines from `memory/watched-repos.md` (everything **above** the `## Trusted Authors` heading).
### 2. Fetch candidate PRs
For each repo:
```bash
gh pr list -R owner/repo --state open --limit 25 \
--json number,title,body,author,isDraft,labels,headRefOid,baseRefName,additions,deletions,files,createdAt,updatedAt,isCrossRepository \
--search "created:>=$(date -u -d '14 days ago' +%Y-%m-%d)"
```
Per-run budget: **≤8 PRs per repo per run** (newest first; remainder logged as overflow).
In single-PR mode, fetch just the one PR:
```bash
gh pr view N -R owner/repo \
--json number,title,body,author,isDraft,labels,headRefOid,baseRefName,additions,deletions,files,createdAt,updatedAt,isCrossRepository
```
### 3. Skip rules (record reason for each skip)
Skip a PR if any of the following hold:
- `isDraft: true` — drafts are work-in-progress
- title matches `^(WIP|\[WIP\]|Draft:)` (case-insensitive)
- has any of the labels `no-triage`, `do-not-merge`, `wip`, `blocked`, `triage:accepted`, `triage:needs-changes`, `triage:deferred`, `triage:out-of-scope` (already triaged)
- author is in the trusted set (see "What 'external' means")
- this PR's `(number, headRefOid)` already appears in `memory/triaged-prs.json["owner/repo"]` (already triaged at this commit; new pushes re-triage)
- the PR already has a comment whose body starts with `**Triage:**` and was posted within the last 7 days (defensive layer in case state file is wiped). Check via:
```bash
gh api repos/owner/repo/issues/NUMBER/comments --jq '.[] | select(.body | startswith("**Triage:**")) | .created_at'
```
If every PR was skipped across all targets, log `PR_TRIAGE_OK no-candidates` and exit (no notification).
### 4. Fetch the diff per remaining PR
```bash
gh pr diff NUMBER -R owner/repo
```
If `additions + deletions > 3000`, do not read the full diff — read only the top-5 largest-delta files via:
```bash
gh api repos/owner/repo/pulls/NUMBER/files --jq 'sort_by(-(.additions + .deletions)) | .[0:5] | .[] | {path: .filename, patch}'
```
If `gh pr diff` fails entirely (e.g. mid-rebase) and the file API also returns empty, skip with reason `empty-diff` (do not block on transient API state).
### 5. Apply the rubric → verdict
Score the PR against four checks. Every check is observable from the diff + metadata, no guesswork.
| Check | Pass condition |
|---|---|
| **Scope** | Touches only `skills/`, `docs/`, `output/`, `catalog/`, `README.md`, `CLAUDE.md`. Touching `.github/workflows/`, `aeon` (root binary), `bin/`, `scripts/`, `apps/mcp-server/`, `apps/dashboard/lib/` requires a maintainer. |
| **Format** | If a `skills/<name>/SKILL.md` is added or modified, the file has YAML frontmatter with `name`, `description`, `var`, `tags` keys. (Skip this check when no SKILL.md is touched.) |
| **Originality** | If a new skill is added, its directory name does not already exist on `main`. Cross-check via `gh api repos/owner/repo/contents/skills` once per run. |
| **Size** | `additions + deletions ≤ 500` lines, OR labelled `large-ok` by a maintainer. |
Verdict assignment (first match wins, in this order):
- **OUT-OF-SCOPE** — Scope check fails AND the touched paths are protected (`.github/workflows/`, `aeon`, `scripts/prefetch-*`, `scripts/postprocess-*`). External contributors cannot ship workflow / runtime changes; redirect them to file an issue.
- **NEEDS-CHANGES** — Format check fails (SKILL.md missing required frontmatter), OR Originality check fails (skill name collides), OR PR body is empty AND additions > 50.
- **DEFER** — Size check fails (>500 lines without `large-ok`), OR PR is marked as RFC / proposal-only in the body, OR the PR depends on an external service that requires a secret the maintainer has not provisioned (mentions of `*_API_KEY` in added code without declaring it in the skill's `requires:`).
- **ACCEPTED** — Otherwise. The PR passes every rubric checSet up and run an Aeon agent instance — get started from scratch, pick which skills to turn on or install more from packs, reschedule or change what runs, edit what an existing skill does, fix a skill that isn't firing, set the STRATEGY.md north star and soul/ voice, turn a coding-agent chat into a scheduled Aeon skill, and mine past coding-agent conversations for recurring work worth automating as a skill. Use when the user mentions Aeon, aeon.yml, an Aeon skill / instance / routine / pack, asks to schedule, enable, edit, or debug an agent that runs on a cron, or asks what of their repeated/manual work Aeon could take over.
Mention/keyword sweep on social platforms for [REPLACE: KEYWORDS] — trends, sentiment, top posts
5 concrete real-life actions, leverage-scored against open loops with specificity and anti-fluff gates
Static config-correctness linter for this instance - catches the silent-failure class (unquoted schedules, duplicate keys, unconfigured skills, mode typos, broken requires/MCP refs) that no run-based health skill can see. Notifies only on problems.
Pull framework updates from the upstream Aeon repo into this instance - 3-way merges canon's new commits into a PR, never clobbering operator config.
Write a publication-ready article in one of three angles - a trending long-form piece, a watched-repo thesis, or a project-through-a-lens essay. Optional Replicate hero image with --visual.
Automatically merge open PRs that have passing CI, no blocking reviews, and no conflicts
Two-mode aeon.yml workflow builder - analyze inspects URLs and emits a tiered, signal-verified skill-enablement plan plus an aeon.yml diff; enable flips slugs to enabled:true and opens a PR.