# list_prs_stale_conflicts — Open PRs Approved, Stale, and Conflicted

**Script name:** `list_prs_stale_conflicts`

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

**Invoke:**
```bash
npx --yes tsx .opencode/skills/auto-agents-system/scripts/list_prs_stale_conflicts.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_conflicts.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 merge conflicts** (`PR.mergeable == false`). These PRs have
been reviewed and approved but cannot progress without manual conflict
resolution by the PR author or an agent with a local clone.

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_with_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_with_conflicts` | Base has advanced AND `PR.mergeable == false` |

**"Stale with 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 == false` — Forgejo has determined that rebasing the PR's commits onto the new base would produce conflicts that cannot be resolved automatically.

This combination requires manual intervention. The resolution path is:
1. Clone the repository locally (or use `git-isolator-util`).
2. Rebase the head branch against the latest base branch.
3. Resolve all merge conflicts.
4. Force-push with lease (`--force-with-lease`).
5. Request re-review if the original approvals are now considered stale.
6. Call [`merge_pr`](../merge_pr/) to initiate the merge.

The server-side rebase endpoint (`POST /pulls/{index}/update?style=rebase`,
used by [`rebase_pr`](../rebase_pr/)) **cannot** be used for these PRs — it
requires `mergeable == true`.


## 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_with_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: 2/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_conflicts` to identify approved PRs that are **blocked by
conflicts** and need manual or agent-driven resolution. Typical use cases:

- **`pr-merge-pool-supervisor`** calls this as **step 4** of its cycle, after
  processing ready-to-merge and stale-clean PRs. For each result it dispatches
  a `pr-merge-worker` to perform a local clone, resolve conflicts, force-push,
  and then trigger the merge.
- A **human-liaison agent** could use this to generate a "conflict backlog"
  report, listing PRs that require author attention.
- A **monitoring alert** could trigger when this bucket grows beyond a
  threshold, signalling that the codebase is accumulating merge debt.

### 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
  3. list_prs_stale_conflicts   ← THIS SCRIPT (third priority)
  4. list_prs_needs_review

Workflow for a PR in this bucket (requires local clone):
  clone repo  →  rebase locally  →  resolve conflicts  →  force-push
  →  merge_pr
```


## Usage Examples

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

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

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

# Get the highest-priority conflicted PR
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_prs_stale_conflicts.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 4** 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_conflicts.ts \
  --url $FORGEJO_URL --pat $FORGEJO_PAT --owner $REPO_OWNER --repo $REPO_NAME)
# If non-empty → dispatch pr-merge-worker for each PR (conflict resolution path)
```

The worker receives `stale_state == stale_with_conflicts` in its prompt and
follows the conflict-resolution path: clone via `git-isolator-util`, rebase
locally, resolve conflicts, force-push via `git-commit-util`, then call
[`merge_pr`](../merge_pr/).

### pr-merge-worker

When dispatched for a PR from this bucket, the worker cannot use the server-
side rebase shortcut. Instead it must:
1. Use `git-isolator-util` to create a local clone with the PR branch.
2. Run `git rebase <base>` locally.
3. Inspect conflict markers; use recent git history to inform resolutions.
4. Stage resolved files and continue the rebase.
5. Use `git-commit-util` to force-push with lease.
6. Call [`merge_pr`](../merge_pr/) to schedule the merge.

### human-liaison-pool-supervisor

The liaison could filter this script's output by `created_at` to identify
PRs that have been stuck in conflict state for more than N days, then post
a comment pinging the PR author to resolve the conflict.

### system-watchdog-pool-supervisor

A growing `list_prs_stale_conflicts` bucket over consecutive watchdog cycles
(while the merge supervisor is running) indicates that conflict resolution
is failing — possibly because the automated resolver is not capable of
handling the specific type of conflict. The watchdog should create a
`Priority/High` announcement.


## Dependencies

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