# list_prs_stale_clean — Open PRs Approved, Behind Base, No Conflicts

**Script name:** `list_prs_stale_clean`

**Script location:** [`scripts/list_prs_stale_clean.ts`](../../../scripts/list_prs_stale_clean.ts)

**Invoke:**
```bash
npx --yes tsx .opencode/skills/auto-agents-system/scripts/list_prs_stale_clean.ts \
  --url URL --pat TOKEN --owner OWNER --repo REPO
```

**CRITICAL:** Always invoke the script using bash and the format noted above. **Never** try to use the skill as a task, or invoke it through any other method than by directly calling bash with a command of the form listed above under "Invoke:".


## Synopsis

```
list_prs_stale_clean.ts --url URL --pat TOKEN --owner OWNER --repo REPO
```

Returns all **open** pull requests that have **at least 1 non-dismissed
approval**, are **stale** (the base branch has advanced since the PR was last
synced), and have **no merge conflicts** (`PR.mergeable == true`). These PRs
are approved and almost ready — they just need a server-side rebase to bring
them current before merging.

This script is a thin wrapper around [`list_prs`](../list_prs/). It
hard-codes the following filters and delegates all logic to that script:

```
state:        'open'
minApprovals: 1
stale:        'stale_no_conflicts'
(no ciStatus filter — all CI states are included)
```


## Arguments

All four arguments are **required**. No optional filter arguments exist — the
filters are baked in and cannot be changed.

| Flag | Type | Description |
|------|------|-------------|
| `--url URL` | string | Forgejo base URL, **no trailing slash** (e.g. `https://git.example.com`) |
| `--pat TOKEN` | string | Personal Access Token with read access to the repository |
| `--owner OWNER` | string | Repository owner or organisation name |
| `--repo REPO` | string | Repository name |
| `-h`, `--help` | — | Print usage to stderr and exit 0 |

Any flag other than these five is **rejected** with exit code 1:
`ERROR: Unknown argument: <flag>`. Filter parameters are hard-coded and cannot
be overridden via CLI flags on this wrapper.

### Invalid Argument Combinations

No optional filter arguments are accepted. Passing any filter-style flag
(e.g. `--min-approvals`, `--stale`, `--state`) causes immediate exit 1.


## Hard-Coded Filters

| Filter | Value | Meaning |
|--------|-------|---------|
| `state` | `open` | Only open PRs are returned |
| `minApprovals` | `1` | At least one non-dismissed `APPROVED` review required |
| `stale` | `stale_no_conflicts` | Base has advanced AND `PR.mergeable == true` |

**"Stale no conflicts"** means two things are simultaneously true:
1. `PR.merge_base != current base branch HEAD` — the base branch has been pushed to since the PR was last synced.
2. `PR.mergeable == true` — Forgejo has determined the PR's commits can be rebased/merged on top of the new base without producing conflicts.

This combination means the rebase can be triggered automatically via the
Forgejo server-side rebase API (`POST /pulls/{index}/update?style=rebase`)
without any manual intervention. See [`rebase_pr`](../rebase_pr/).


## Output

Written to **stdout** as a pretty-printed JSON array sorted by `priority_rank`
(highest priority first). Each element is the full Forgejo PR object augmented
with four fields:

| Field | Type | Description |
|-------|------|-------------|
| `approvals_count` | integer | Will always be ≥ 1 for results of this script |
| `stale_state` | string | Will always be `stale_no_conflicts` for results of this script |
| `ci_status` | string | `passing`, `failing`, `pending`, or `unknown` — **not filtered**; any CI state is included |
| `priority_rank` | integer | 0 (highest) through 6 (unlabelled) |
| `priority_label` | string \| null | Matched `Priority/*` label name, or `null` |

An **empty array** `[]` means no open PR currently matches all three conditions.

See [`list_prs`](../list_prs/) for the full output schema documentation.


## Diagnostics / stderr

Progress messages are written to stderr and do not affect the JSON output:

```
Fetching PRs from https://git.example.com/org/repo (state=open)...
Fetched 42 PR(s).  Enriching and filtering...
Fetching HEAD SHAs for 3 unique base branch(es)...
Fetching reviews for 42 PR(s) concurrently (limit=20)...
Fetching CI status for 42 PR(s) concurrently (limit=20)...

Result: 3/42 PR(s) matched the filter criteria.
```


## Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Success — JSON array written to stdout (may be `[]`) |
| 1 | Fatal error — message written to stderr |


## Errors the Script May Throw

| Condition | Behaviour |
|-----------|-----------|
| Missing `--url`, `--pat`, `--owner`, or `--repo` | Exit 1: `ERROR: --<flag> is required` |
| Any unknown flag passed | Exit 1: `ERROR: Unknown argument: <flag>` |
| Forgejo API non-2xx on PR list | Exit 1 with HTTP status and body detail |
| Forgejo API failure for a single branch or review | Warning to stderr; processing continues |
| Network timeout (30 s per request) | Exit 1 with error message |


## When to Use This Script

Use `list_prs_stale_clean` when you need to identify approved PRs that can be
**brought up-to-date automatically** (no human conflict resolution needed).
Typical use cases:

- **`pr-merge-pool-supervisor`** calls this as **step 3** of its cycle (after
  processing ready-to-merge PRs). For each result it dispatches a
  `pr-merge-worker` to trigger a server-side rebase, then re-checks step 2
  before continuing — because a successful rebase moves the PR from this
  bucket into `list_prs_ready_to_merge`.
- A **rebase automation** script could use this as its sole input to keep all
  approved PRs current without any merge logic.
- A **monitoring agent** could track this bucket's size over time as a
  measure of base-branch churn.

### Position in the Four-Bucket System

```
pr-merge-pool-supervisor processes buckets in this order:
  1. list_prs_ready_to_merge
  2. list_prs_stale_clean       ← THIS SCRIPT (second priority)
  3. list_prs_stale_conflicts
  4. list_prs_needs_review

Workflow for a PR in this bucket:
  rebase_pr  →  moves PR to list_prs_ready_to_merge  →  merge_pr
```


## Usage Examples

```bash
# Standard usage
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_stale_clean.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo

# Count stale-but-clean PRs awaiting rebase
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_stale_clean.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq 'length'

# Get PR numbers and their base branches (useful for batch rebase planning)
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_stale_clean.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq '[.[] | {number, title, base: .base.ref}]'

# Get the top-priority stale-clean PR
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_stale_clean.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq 'first'
```


## Agent Usage Examples

### pr-merge-pool-supervisor

The supervisor calls this script as **step 3** of its main cycle. If the
list is non-empty, it dispatches `pr-merge-worker` sequentially for each PR
and then restarts the cycle at step 1.

```bash
result=$(npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_stale_clean.ts \
  --url $FORGEJO_URL --pat $FORGEJO_PAT --owner $REPO_OWNER --repo $REPO_NAME)
# If non-empty → dispatch pr-merge-worker for each PR
```

The worker receives the PR's `stale_state == stale_no_conflicts` in its
prompt and follows the "stale, no conflicts" path: call
[`rebase_pr`](../rebase_pr/) first, then call [`merge_pr`](../merge_pr/).

### pr-merge-worker

When dispatched for a PR from this bucket, the worker:
1. Calls [`rebase_pr`](../rebase_pr/) — triggers server-side rebase
2. Calls [`merge_pr`](../merge_pr/) — schedules the merge

The worker does **not** need to clone the repo or resolve conflicts — the
`stale_no_conflicts` state guarantees the server can do it automatically.

### system-watchdog-pool-supervisor

The watchdog can compare the sizes of the `list_prs_stale_clean` and
`list_prs_stale_conflicts` buckets over time. A growing `stale_clean`
bucket paired with a shrinking `list_prs_ready_to_merge` bucket suggests
the merge supervisor is running but rebases are not completing successfully.


## Dependencies

- Node.js ≥ 18 (native `fetch` API)
- `npx tsx` for TypeScript execution
- Zero external npm packages
