# list_prs_ready_to_merge — Open PRs Approved, Up-to-Date, and CI Passing

**Script name:** `list_prs_ready_to_merge`

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

**Invoke:**
```bash
npx --yes tsx .opencode/skills/auto-agents-system/scripts/list_prs_ready_to_merge.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_ready_to_merge.ts --url URL --pat TOKEN --owner OWNER --repo REPO
```

Returns all **open** pull requests that have **at least 1 non-dismissed
approval**, are **not stale** (the PR's head branch is already current with
its base branch), and have **passing CI** (all commit status checks have
succeeded). These PRs are ready to be merged immediately — no rebase,
conflict resolution, or CI wait is required.

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:        'not_stale'
ciStatus:     ['passing']
```


## 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 and an error
message: `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 on this script. Passing any
filter-style flag (e.g. `--min-approvals`, `--stale`, `--state`, etc.) causes
immediate exit 1 — those flags are not accepted by this wrapper.


## Hard-Coded Filters

| Filter | Value | Meaning |
|--------|-------|---------|
| `state` | `open` | Only open PRs are returned |
| `minApprovals` | `1` | At least one non-dismissed `APPROVED` review required |
| `stale` | `not_stale` | `PR.merge_base == current base branch HEAD` |
| `ciStatus` | `['passing']` | All CI checks must have succeeded (`ci_status == 'passing'`) |

**"Not stale"** means `PR.merge_base` (the common ancestor SHA between the PR
head and the base) equals the current HEAD of the base branch. In other words,
the base branch has not advanced since the PR was last synced — the PR can be
merged into the base branch without a rebase.

**"At least 1 approval"** means at least one reviewer has their most recent
review in `APPROVED` state (non-dismissed). A reviewer who approved then later
submitted `REQUEST_CHANGES` is not counted.

**"CI passing"** means the Forgejo combined commit-status for the PR's head
SHA returned `state == 'success'`. PRs with `pending` CI (still running),
`failing` CI (at least one check failed), or `unknown` CI (no checks reported)
are excluded. This prevents merging code before the full test suite has
confirmed it is safe.


## 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 `not_stale` for results of this script |
| `ci_status` | string | Will always be `passing` for results of this script |
| `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 satisfies both 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: 5/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_ready_to_merge` when you need a list of PRs that can be merged
**right now** without any intermediate steps. Typical use cases:

- **`pr-merge-pool-supervisor`** calls this as the **first and highest-priority
  step** in its cycle. Processing these PRs first ensures that already-approved,
  current PRs are never delayed while rebase or review work is happening.
- A **monitoring agent** can use this as a health indicator: a consistently
  non-empty result that never shrinks suggests the merge process is blocked.
- A **deployment pipeline** trigger could use this to determine whether any
  code is ready for release.

### Priority in the Four-Bucket System

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

When this bucket is non-empty, the supervisor dispatches workers for
ALL PRs in it and then restarts the cycle at step 1.
```


## Usage Examples

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

# Count ready-to-merge PRs
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_ready_to_merge.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq 'length'

# Extract PR numbers for iteration
READY_PRS=$(npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_ready_to_merge.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq '[.[].number]')

# Get only the top-priority ready PR
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_ready_to_merge.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 2** (the first work step) of its
main cycle. If the list is non-empty, it dispatches `pr-merge-worker`
sequentially for each PR and then restarts the cycle from step 1.

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

The supervisor passes each PR's full JSON object (number, title, head SHA,
base branch, staleness, approval count, etc.) in the `pr-merge-worker` prompt.
Since results come from this script, the worker knows the PR is already
approved and not stale — it calls [`merge_pr`](../merge_pr/) directly without
needing to rebase first.

### pr-merge-worker

When dispatched for a PR found by `list_prs_ready_to_merge`, the worker's
decision tree is simple: the PR is approved and current, so it calls
[`merge_pr`](../merge_pr/) immediately without checking for staleness or
conflicts.

### system-watchdog-pool-supervisor

The watchdog could call this periodically to verify that the merge pipeline
is healthy. A PR that appears in `list_prs_ready_to_merge` for many
consecutive watchdog cycles without being merged indicates the
`pr-merge-pool-supervisor` may be stuck or absent.


## Dependencies

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