build: fixed pr-merge filtering to avoid non-mergable PRs
CI / benchmark-regression (push) Failing after 0s
CI / benchmark-publish (push) Failing after 0s
CI / push-validation (push) Successful in 28s
CI / helm (push) Successful in 45s
CI / build (push) Successful in 4m2s
CI / lint (push) Successful in 4m14s
CI / quality (push) Successful in 4m31s
CI / typecheck (push) Successful in 4m55s
CI / security (push) Successful in 5m14s
CI / e2e_tests (push) Successful in 7m17s
CI / integration_tests (push) Successful in 8m0s
CI / unit_tests (push) Successful in 9m18s
CI / docker (push) Failing after 46s
CI / coverage (push) Successful in 14m52s
CI / status-check (push) Failing after 4s
CI / benchmark-regression (push) Failing after 0s
CI / benchmark-publish (push) Failing after 0s
CI / push-validation (push) Successful in 28s
CI / helm (push) Successful in 45s
CI / build (push) Successful in 4m2s
CI / lint (push) Successful in 4m14s
CI / quality (push) Successful in 4m31s
CI / typecheck (push) Successful in 4m55s
CI / security (push) Successful in 5m14s
CI / e2e_tests (push) Successful in 7m17s
CI / integration_tests (push) Successful in 8m0s
CI / unit_tests (push) Successful in 9m18s
CI / docker (push) Failing after 46s
CI / coverage (push) Successful in 14m52s
CI / status-check (push) Failing after 4s
This commit is contained in:
@@ -287,13 +287,13 @@ See [`scripts/`](./references/scripts/) for the full index with invocation instr
|
||||
|
||||
| Script | Reference Docs | Script File | Description |
|
||||
|--------|---------------|-------------|-------------|
|
||||
| `list_prs` | [docs](./references/scripts/list_prs/) | [list_prs.ts](./scripts/list_prs.ts) | General-purpose PR lister with all filter options; core module imported by the six wrappers below |
|
||||
| `list_prs_ready_to_merge` | [docs](./references/scripts/list_prs_ready_to_merge/) | [list_prs_ready_to_merge.ts](./scripts/list_prs_ready_to_merge.ts) | Open PRs with ≥ 1 approval, not stale — ready to merge immediately |
|
||||
| `list_prs_stale_clean` | [docs](./references/scripts/list_prs_stale_clean/) | [list_prs_stale_clean.ts](./scripts/list_prs_stale_clean.ts) | Open PRs with ≥ 1 approval, stale but no conflicts — server-side rebase then merge |
|
||||
| `list_prs_stale_conflicts` | [docs](./references/scripts/list_prs_stale_conflicts/) | [list_prs_stale_conflicts.ts](./scripts/list_prs_stale_conflicts.ts) | Open PRs with ≥ 1 approval, stale with conflicts — local clone, resolve, force-push, then merge |
|
||||
| `list_prs_needs_review_not_stale` | [docs](./references/scripts/list_prs_needs_review_not_stale/) | [list_prs_needs_review_not_stale.ts](./scripts/list_prs_needs_review_not_stale.ts) | Open PRs with zero approvals, not stale — review only needed; merges immediately on approval |
|
||||
| `list_prs_needs_review_stale_clean` | [docs](./references/scripts/list_prs_needs_review_stale_clean/) | [list_prs_needs_review_stale_clean.ts](./scripts/list_prs_needs_review_stale_clean.ts) | Open PRs with zero approvals, stale but no conflicts — review + server-side rebase needed |
|
||||
| `list_prs_needs_review_stale_conflicts` | [docs](./references/scripts/list_prs_needs_review_stale_conflicts/) | [list_prs_needs_review_stale_conflicts.ts](./scripts/list_prs_needs_review_stale_conflicts.ts) | Open PRs with zero approvals, stale with conflicts — review + local conflict resolution needed |
|
||||
| `list_prs` | [docs](./references/scripts/list_prs/) | [list_prs.ts](./scripts/list_prs.ts) | General-purpose PR lister with all filter options (`--stale`, `--ci-status`, `--min-approvals`, …); core module imported by the six wrappers below |
|
||||
| `list_prs_ready_to_merge` | [docs](./references/scripts/list_prs_ready_to_merge/) | [list_prs_ready_to_merge.ts](./scripts/list_prs_ready_to_merge.ts) | Open PRs with ≥ 1 approval, not stale, **and CI passing** — ready to merge immediately |
|
||||
| `list_prs_stale_clean` | [docs](./references/scripts/list_prs_stale_clean/) | [list_prs_stale_clean.ts](./scripts/list_prs_stale_clean.ts) | Open PRs with ≥ 1 approval, stale but no conflicts, any CI status — server-side rebase then merge |
|
||||
| `list_prs_stale_conflicts` | [docs](./references/scripts/list_prs_stale_conflicts/) | [list_prs_stale_conflicts.ts](./scripts/list_prs_stale_conflicts.ts) | Open PRs with ≥ 1 approval, stale with conflicts, any CI status — local clone, resolve, force-push, then merge |
|
||||
| `list_prs_needs_review_not_stale` | [docs](./references/scripts/list_prs_needs_review_not_stale/) | [list_prs_needs_review_not_stale.ts](./scripts/list_prs_needs_review_not_stale.ts) | Open PRs with zero approvals, not stale, any CI status — review only needed; merges immediately on approval |
|
||||
| `list_prs_needs_review_stale_clean` | [docs](./references/scripts/list_prs_needs_review_stale_clean/) | [list_prs_needs_review_stale_clean.ts](./scripts/list_prs_needs_review_stale_clean.ts) | Open PRs with zero approvals, stale but no conflicts, any CI status — review + server-side rebase needed |
|
||||
| `list_prs_needs_review_stale_conflicts` | [docs](./references/scripts/list_prs_needs_review_stale_conflicts/) | [list_prs_needs_review_stale_conflicts.ts](./scripts/list_prs_needs_review_stale_conflicts.ts) | Open PRs with zero approvals, stale with conflicts, any CI status — review + local conflict resolution needed |
|
||||
| `merge_pr` | [docs](./references/scripts/merge_pr/) | [merge_pr.ts](./scripts/merge_pr.ts) | Initiates a rebase-style merge with automerge scheduling; handles open issue dependencies |
|
||||
| `rebase_pr` | [docs](./references/scripts/rebase_pr/) | [rebase_pr.ts](./scripts/rebase_pr.ts) | Triggers a Forgejo server-side rebase on a stale, conflict-free PR — no local clone required |
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ product-builder (AUTO-PROD-BLDR) ← process supervisor (like systemd)
|
||||
**Workers**: `pr-merge-worker` via **blocking Task tool call** (not async)
|
||||
**Worker tag pattern**: NONE — no `[AUTO-PRMRG-N]` sessions ever exist
|
||||
|
||||
**Role**: Merges approved PRs. Verifies seven merge criteria. Handles pre-merge rebasing.
|
||||
**Role**: Merges approved PRs. Verifies merge criteria. Handles pre-merge rebasing.
|
||||
|
||||
**⚠️ Special**: Does NOT use async-agent-manager. Calls `pr-merge-worker` directly as a Task subagent and blocks until it completes. When the supervisor is busy executing its worker, it appears as a normal busy session — this is correct behavior. Product-builder and system-watchdog must NOT flag the absence of `[AUTO-PRMRG-N]` sessions as a problem.
|
||||
|
||||
@@ -108,7 +108,7 @@ product-builder (AUTO-PROD-BLDR) ← process supervisor (like systemd)
|
||||
**Definition**: `bug-hunt-pool-supervisor`
|
||||
**Worker count**: N_QUARTER
|
||||
**Sleep**: 900 seconds (15 minutes)
|
||||
**Workers**: `bug-hunt-worker` (async sessions, use Gemini 2.5 Pro for large context)
|
||||
**Workers**: `bug-hunt-worker` (async sessions)
|
||||
**Worker tag pattern**: `[AUTO-BUG-N]`
|
||||
|
||||
**Role**: Proactive bug detection. Maps source modules, dispatches workers for deep code analysis combined with specification comparison.
|
||||
@@ -119,7 +119,7 @@ product-builder (AUTO-PROD-BLDR) ← process supervisor (like systemd)
|
||||
**Definition**: `test-infra-pool-supervisor`
|
||||
**Worker count**: N_QUARTER
|
||||
**Sleep**: 900 seconds (15 minutes)
|
||||
**Workers**: `test-infra-worker` (async sessions, use Gemini 2.5 Pro)
|
||||
**Workers**: `test-infra-worker` (async sessions)
|
||||
**Worker tag pattern**: `[AUTO-INF-N]`
|
||||
|
||||
**Role**: Testing infrastructure improvement. Analyzes CI timing, coverage gaps, test architecture. Never disables or weakens existing checks — only proposes additions and optimizations.
|
||||
|
||||
@@ -17,13 +17,13 @@ buckets. Together they cover the complete PR pipeline used by
|
||||
|
||||
| Script | One-liner |
|
||||
|--------|-----------|
|
||||
| [`list_prs`](./list_prs/) | General-purpose PR lister — all filter options, importable as a module by the six wrappers below |
|
||||
| [`list_prs_ready_to_merge`](./list_prs_ready_to_merge/) | Open PRs with ≥ 1 approval that are already current with their base branch — merge immediately |
|
||||
| [`list_prs_stale_clean`](./list_prs_stale_clean/) | Open PRs with ≥ 1 approval that are behind their base branch but have no conflicts — server-side rebase then merge |
|
||||
| [`list_prs_stale_conflicts`](./list_prs_stale_conflicts/) | Open PRs with ≥ 1 approval that are behind their base branch and have merge conflicts — local clone, resolve, force-push, then merge |
|
||||
| [`list_prs_needs_review_not_stale`](./list_prs_needs_review_not_stale/) | Open PRs with zero approvals that are current with their base branch — review only needed |
|
||||
| [`list_prs_needs_review_stale_clean`](./list_prs_needs_review_stale_clean/) | Open PRs with zero approvals that are stale but conflict-free — review + server-side rebase needed |
|
||||
| [`list_prs_needs_review_stale_conflicts`](./list_prs_needs_review_stale_conflicts/) | Open PRs with zero approvals that are stale with conflicts — review + local conflict resolution needed |
|
||||
| [`list_prs`](./list_prs/) | General-purpose PR lister — all filter options (`--stale`, `--ci-status`, `--min-approvals`, …), importable as a module by the six wrappers below |
|
||||
| [`list_prs_ready_to_merge`](./list_prs_ready_to_merge/) | Open PRs with ≥ 1 approval, already current with their base branch, **and CI passing** — merge immediately |
|
||||
| [`list_prs_stale_clean`](./list_prs_stale_clean/) | Open PRs with ≥ 1 approval, behind their base branch, no conflicts, any CI status — server-side rebase then merge |
|
||||
| [`list_prs_stale_conflicts`](./list_prs_stale_conflicts/) | Open PRs with ≥ 1 approval, behind their base branch, merge conflicts present, any CI status — local clone, resolve, force-push, then merge |
|
||||
| [`list_prs_needs_review_not_stale`](./list_prs_needs_review_not_stale/) | Open PRs with zero approvals, current with their base branch, any CI status — review only needed |
|
||||
| [`list_prs_needs_review_stale_clean`](./list_prs_needs_review_stale_clean/) | Open PRs with zero approvals, stale but conflict-free, any CI status — review + server-side rebase needed |
|
||||
| [`list_prs_needs_review_stale_conflicts`](./list_prs_needs_review_stale_conflicts/) | Open PRs with zero approvals, stale with conflicts, any CI status — review + local conflict resolution needed |
|
||||
|
||||
### PR Action Scripts
|
||||
|
||||
@@ -36,34 +36,37 @@ buckets. Together they cover the complete PR pipeline used by
|
||||
## The Six-Bucket Pipeline
|
||||
|
||||
The six listing scripts define a complete, non-overlapping classification of
|
||||
all open PRs. The two approval states (approved vs needs review) are each
|
||||
further split by three staleness states, giving six buckets in total.
|
||||
open PRs. The two approval states (approved vs needs review) are each split
|
||||
by three staleness states, giving six buckets in total. **Bucket 1 additionally
|
||||
requires CI to be passing**; approved + not-stale PRs with failing or pending CI
|
||||
are held out of all buckets until their CI resolves.
|
||||
|
||||
```
|
||||
Every open PR belongs to exactly one bucket at any moment:
|
||||
Every open PR belongs to exactly one bucket at any moment
|
||||
(or is temporarily absent if CI is blocking it from Bucket 1):
|
||||
|
||||
── Approved (≥ 1 non-dismissed approval) ──────────────────────────────────
|
||||
Bucket 1 — list_prs_ready_to_merge (approved + not stale)
|
||||
Bucket 2 — list_prs_stale_clean (approved + stale, no conflicts)
|
||||
Bucket 3 — list_prs_stale_conflicts (approved + stale, has conflicts)
|
||||
Bucket 1 — list_prs_ready_to_merge (approved + not stale + CI passing)
|
||||
Bucket 2 — list_prs_stale_clean (approved + stale, no conflicts, any CI)
|
||||
Bucket 3 — list_prs_stale_conflicts (approved + stale, has conflicts, any CI)
|
||||
|
||||
── Needs Review (0 approvals) ─────────────────────────────────────────────
|
||||
Bucket 4 — list_prs_needs_review_not_stale (no approvals + not stale)
|
||||
Bucket 5 — list_prs_needs_review_stale_clean (no approvals + stale, no conflicts)
|
||||
Bucket 6 — list_prs_needs_review_stale_conflicts (no approvals + stale, has conflicts)
|
||||
Bucket 4 — list_prs_needs_review_not_stale (no approvals + not stale, any CI)
|
||||
Bucket 5 — list_prs_needs_review_stale_clean (no approvals + stale, no conflicts, any CI)
|
||||
Bucket 6 — list_prs_needs_review_stale_conflicts (no approvals + stale, has conflicts, any CI)
|
||||
|
||||
Exception: PRs with stale_state == stale_unknown (Forgejo still computing
|
||||
conflict status) are transiently absent from all buckets. They appear in
|
||||
the appropriate bucket within seconds once Forgejo finishes.
|
||||
── Temporarily absent (not in any bucket) ─────────────────────────────────
|
||||
• stale_state == stale_unknown → Forgejo still computing conflict status; appears shortly
|
||||
• approved + not stale + CI failing/pending/unknown → held out of Bucket 1 until CI passes
|
||||
```
|
||||
|
||||
Typical action sequence per bucket:
|
||||
|
||||
```
|
||||
Bucket 1: merge_pr
|
||||
Bucket 1: merge_pr (CI already green — safe to merge immediately)
|
||||
Bucket 2: rebase_pr → merge_pr
|
||||
Bucket 3: clone → rebase locally → resolve conflicts → force-push → merge_pr
|
||||
Bucket 4: (dispatch reviewer → on approval, moves to Bucket 1)
|
||||
Bucket 4: (dispatch reviewer → on approval, moves to Bucket 1 if CI passes)
|
||||
Bucket 5: (dispatch reviewer → on approval, moves to Bucket 2)
|
||||
Bucket 6: (dispatch reviewer → on approval, moves to Bucket 3)
|
||||
```
|
||||
@@ -83,9 +86,18 @@ npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/<script>.ts \
|
||||
|
||||
`merge_pr` and `rebase_pr` additionally require `--pr <number>`.
|
||||
`merge_pr` has an optional `--dep error|delete|reverse` flag.
|
||||
The six `list_prs_*` wrappers accept **only** the four connection flags.
|
||||
`list_prs` itself accepts optional filter flags (`--state`, `--min-approvals`,
|
||||
`--max-approvals`, `--mergeable`, `--stale`).
|
||||
The six `list_prs_*` wrappers accept **only** the four connection flags —
|
||||
their filter parameters are hard-coded and cannot be overridden on the CLI.
|
||||
`list_prs` itself accepts optional filter flags: `--state`, `--min-approvals`,
|
||||
`--max-approvals`, `--mergeable`, `--stale`, and `--ci-status`.
|
||||
|
||||
**`--ci-status`** accepts a comma-separated whitelist of CI states to include:
|
||||
`passing`, `failing`, `pending`, `unknown` (any combination). Omit to include
|
||||
all CI states. Example: `--ci-status passing,pending`
|
||||
|
||||
Each PR object in all outputs now includes a `ci_status` field
|
||||
(`passing` | `failing` | `pending` | `unknown`) derived from the Forgejo
|
||||
combined commit-status endpoint for the PR's head SHA.
|
||||
|
||||
All scripts write JSON to **stdout** and progress/diagnostics to **stderr**.
|
||||
Exit code `0` = success, `1` = error.
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
npx --yes tsx .opencode/skills/auto-agents-system/scripts/list_prs.ts \
|
||||
--url URL --pat TOKEN --owner OWNER --repo REPO \
|
||||
[--state open|closed|all] [--min-approvals N] [--max-approvals N] \
|
||||
[--mergeable true|false] [--stale not_stale|stale_no_conflicts|stale_with_conflicts|stale_unknown]
|
||||
[--mergeable true|false] [--stale not_stale|stale_no_conflicts|stale_with_conflicts|stale_unknown] \
|
||||
[--ci-status passing|failing|pending|unknown[,...]]
|
||||
```
|
||||
|
||||
**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:".
|
||||
@@ -56,6 +57,7 @@ All filter flags default to "no filter" (i.e. all values pass) when omitted.
|
||||
| `--max-approvals N` | integer ≥ 0 | (none) | Include only PRs with **≤ N** non-dismissed approvals |
|
||||
| `--mergeable true\|false` | boolean | (none) | Filter by Forgejo's raw `PR.mergeable` field |
|
||||
| `--stale VALUE` | string | (none) | Filter by computed `stale_state` (see Stale States below) |
|
||||
| `--ci-status VALUE[,...]` | string | (none) | Comma-separated whitelist of CI statuses to include (see CI Status below). Omit to include all CI statuses. |
|
||||
| `-h`, `--help` | — | — | Print usage to stderr and exit 0 |
|
||||
|
||||
### Argument Validity Rules
|
||||
@@ -63,6 +65,7 @@ All filter flags default to "no filter" (i.e. all values pass) when omitted.
|
||||
- `--min-approvals` and `--max-approvals` may be used together; both constraints apply simultaneously (logical AND).
|
||||
- `--min-approvals 1 --max-approvals 0` is logically impossible and will return an empty result set — the script does not reject it, it simply matches nothing.
|
||||
- `--mergeable` filters on the raw API field, not on the computed `stale_state`. Use `--stale` for the semantically richer classification.
|
||||
- `--ci-status` accepts a comma-separated list of values from `passing`, `failing`, `pending`, `unknown`. A PR must match **any one** of the listed values (logical OR within the list). Multiple `--ci-status` flags are not supported — put all desired values in one comma-separated argument.
|
||||
- Any unknown flag causes exit code 1 and a usage message.
|
||||
- `--state` only accepts the three literal values `open`, `closed`, `all`; any other value is passed to the API which may behave unexpectedly (this is not validated client-side).
|
||||
|
||||
@@ -76,6 +79,7 @@ Forgejo PR object as returned by the API, augmented with four additional fields:
|
||||
|-------|------|-------------|
|
||||
| `approvals_count` | integer | Count of distinct non-dismissed `APPROVED` reviews. Per-reviewer: only the reviewer's most recent review is counted; if they later submitted `REQUEST_CHANGES`, that supersedes the approval and it is not counted. |
|
||||
| `stale_state` | string | One of five stale states (see below) |
|
||||
| `ci_status` | string | One of four CI status values (see CI Status below) |
|
||||
| `priority_rank` | integer | 0 = `Priority/CI Blocker` (highest), …, 5 = `Priority/Backlog`, 6 = unlabelled |
|
||||
| `priority_label` | string \| null | The matched `Priority/*` label name, or `null` if none present |
|
||||
|
||||
@@ -92,6 +96,19 @@ first within the result set.
|
||||
| `stale_unknown` | Base has advanced AND `PR.mergeable == null` — Forgejo is still computing |
|
||||
| `compute_error` | `PR.merge_base` or base HEAD SHA is missing; classification impossible |
|
||||
|
||||
### CI Status
|
||||
|
||||
Derived from `GET /api/v1/repos/{owner}/{repo}/commits/{head_sha}/status` — the Forgejo combined commit-status endpoint. The `state` field of that response is mapped as follows:
|
||||
|
||||
| `ci_status` value | Forgejo combined state | Meaning |
|
||||
|-------------------|----------------------|---------|
|
||||
| `passing` | `success` | All status checks have reported success |
|
||||
| `failing` | `failure` \| `error` \| `warning` | At least one check has failed or errored |
|
||||
| `pending` | `pending` | Checks are queued or still running |
|
||||
| `unknown` | `""` \| `unknown` | No status checks have been reported for this commit |
|
||||
|
||||
Use `--ci-status passing` to restrict to PRs where all CI has passed. Use `--ci-status passing,pending` to include both passing and still-running PRs. Omit `--ci-status` entirely to return PRs regardless of their CI state.
|
||||
|
||||
### Priority Rank Table
|
||||
|
||||
| `priority_rank` | Label |
|
||||
@@ -115,6 +132,7 @@ 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: 12/42 PR(s) matched the filter criteria.
|
||||
```
|
||||
@@ -165,6 +183,9 @@ served by one of the four wrapper scripts:
|
||||
- Find PRs with exactly 2 approvals: `--min-approvals 2 --max-approvals 2`
|
||||
- Find approved PRs that are still pending Forgejo's conflict check: `--min-approvals 1 --stale stale_unknown`
|
||||
- List all PRs regardless of state: `--state all`
|
||||
- Find open PRs ready to merge (approved, not stale, CI passing): `--min-approvals 1 --stale not_stale --ci-status passing`
|
||||
- Find PRs with broken CI: `--ci-status failing`
|
||||
- Find PRs with passing or pending CI: `--ci-status passing,pending`
|
||||
|
||||
For the four standard operating buckets used by `pr-merge-pool-supervisor`,
|
||||
prefer the thin wrappers — they are self-documenting and require fewer flags.
|
||||
@@ -264,8 +285,16 @@ const filters: Filters = {
|
||||
state: 'open',
|
||||
minApprovals: 1,
|
||||
stale: 'not_stale',
|
||||
ciStatus: ['passing'], // only PRs with all CI checks passing
|
||||
};
|
||||
|
||||
// ciStatus accepts any combination:
|
||||
// ciStatus: ['passing'] — CI must have passed
|
||||
// ciStatus: ['pending'] — CI still running
|
||||
// ciStatus: ['failing'] — CI has failed
|
||||
// ciStatus: ['passing', 'pending'] — CI passed or still running
|
||||
// (omit ciStatus entirely) — any CI state passes through
|
||||
|
||||
const prs: AugmentedPR[] = await listPRs(config, filters);
|
||||
```
|
||||
|
||||
|
||||
+3
@@ -35,6 +35,7 @@ hard-codes the following filters and delegates all logic to that script:
|
||||
state: 'open'
|
||||
maxApprovals: 0
|
||||
stale: 'not_stale'
|
||||
(no ciStatus filter — all CI states are included)
|
||||
```
|
||||
|
||||
|
||||
@@ -89,6 +90,7 @@ with four fields:
|
||||
|-------|------|-------------|
|
||||
| `approvals_count` | integer | Will always be `0` for results of this script |
|
||||
| `stale_state` | string | Will always be `not_stale` 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` |
|
||||
|
||||
@@ -107,6 +109,7 @@ 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: 8/42 PR(s) matched the filter criteria.
|
||||
```
|
||||
|
||||
+3
@@ -32,6 +32,7 @@ hard-codes the following filters and delegates all logic to that script:
|
||||
state: 'open'
|
||||
maxApprovals: 0
|
||||
stale: 'stale_no_conflicts'
|
||||
(no ciStatus filter — all CI states are included)
|
||||
```
|
||||
|
||||
|
||||
@@ -85,6 +86,7 @@ with four fields:
|
||||
|-------|------|-------------|
|
||||
| `approvals_count` | integer | Will always be `0` 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` |
|
||||
|
||||
@@ -103,6 +105,7 @@ 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.
|
||||
```
|
||||
|
||||
+3
@@ -32,6 +32,7 @@ hard-codes the following filters and delegates all logic to that script:
|
||||
state: 'open'
|
||||
maxApprovals: 0
|
||||
stale: 'stale_with_conflicts'
|
||||
(no ciStatus filter — all CI states are included)
|
||||
```
|
||||
|
||||
|
||||
@@ -85,6 +86,7 @@ with four fields:
|
||||
|-------|------|-------------|
|
||||
| `approvals_count` | integer | Will always be `0` 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` |
|
||||
|
||||
@@ -103,6 +105,7 @@ 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.
|
||||
```
|
||||
|
||||
+15
-4
@@ -1,4 +1,4 @@
|
||||
# list_prs_ready_to_merge — Open PRs Approved and Up-to-Date
|
||||
# list_prs_ready_to_merge — Open PRs Approved, Up-to-Date, and CI Passing
|
||||
|
||||
**Script name:** `list_prs_ready_to_merge`
|
||||
|
||||
@@ -19,9 +19,10 @@ 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** and are **not stale** (the PR's head branch is already current with
|
||||
its base branch). These PRs are ready to be merged immediately — no rebase
|
||||
or conflict resolution is required.
|
||||
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:
|
||||
@@ -30,6 +31,7 @@ hard-codes the following filters and delegates all logic to that script:
|
||||
state: 'open'
|
||||
minApprovals: 1
|
||||
stale: 'not_stale'
|
||||
ciStatus: ['passing']
|
||||
```
|
||||
|
||||
|
||||
@@ -64,6 +66,7 @@ immediate exit 1 — those flags are not accepted by this wrapper.
|
||||
| `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,
|
||||
@@ -74,6 +77,12 @@ merged into the base branch without a rebase.
|
||||
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
|
||||
|
||||
@@ -85,6 +94,7 @@ with four fields:
|
||||
|-------|------|-------------|
|
||||
| `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` |
|
||||
|
||||
@@ -102,6 +112,7 @@ 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.
|
||||
```
|
||||
|
||||
@@ -32,6 +32,7 @@ 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)
|
||||
```
|
||||
|
||||
|
||||
@@ -85,6 +86,7 @@ with four fields:
|
||||
|-------|------|-------------|
|
||||
| `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` |
|
||||
|
||||
@@ -102,6 +104,7 @@ 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.
|
||||
```
|
||||
|
||||
+3
@@ -31,6 +31,7 @@ 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)
|
||||
```
|
||||
|
||||
|
||||
@@ -92,6 +93,7 @@ with four fields:
|
||||
|-------|------|-------------|
|
||||
| `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` |
|
||||
|
||||
@@ -109,6 +111,7 @@ 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.
|
||||
```
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
// USAGE (as CLI):
|
||||
// list_prs.ts --url URL --pat TOKEN --owner OWNER --repo REPO [OPTIONS]
|
||||
//
|
||||
// Notable filter options:
|
||||
// --stale not_stale|stale_no_conflicts|stale_with_conflicts|stale_unknown
|
||||
// --ci-status passing|failing|pending|unknown (comma-separated, any combination)
|
||||
//
|
||||
// USAGE (as module, imported by the four wrapper scripts):
|
||||
// import { listPRs, parseConnectionArgs } from './list_prs.ts';
|
||||
// =============================================================================
|
||||
@@ -35,6 +39,13 @@ export type StaleState =
|
||||
| 'stale_unknown' // base advanced; mergeable=null (Forgejo pending)
|
||||
| 'compute_error'; // missing data or branch API failure
|
||||
|
||||
// CIStatus — combined commit-status result for the PR head SHA
|
||||
// 'passing' — all checks reported success (Forgejo combined state == 'success')
|
||||
// 'failing' — at least one check failed/errored (state == 'failure'|'error'|'warning')
|
||||
// 'pending' — checks are queued or still running (state == 'pending')
|
||||
// 'unknown' — no checks have been reported yet (state == ''|'unknown')
|
||||
export type CIStatus = 'passing' | 'failing' | 'pending' | 'unknown';
|
||||
|
||||
export interface Config {
|
||||
url: string; // Forgejo base URL, no trailing slash
|
||||
pat: string; // Personal Access Token
|
||||
@@ -48,6 +59,7 @@ export interface Filters {
|
||||
maxApprovals?: number; // approvals_count <= N
|
||||
mergeable?: boolean; // filter on PR.mergeable directly
|
||||
stale?: StaleState; // filter on computed stale_state
|
||||
ciStatus?: CIStatus[]; // whitelist: PR's ci_status must be one of these values
|
||||
}
|
||||
|
||||
// Raw shape of a Forgejo pull request object (only the fields we use).
|
||||
@@ -55,6 +67,7 @@ export interface Filters {
|
||||
interface RawPR {
|
||||
number: number;
|
||||
title: string;
|
||||
head: { sha: string };
|
||||
base: { ref: string };
|
||||
merge_base?: string | null;
|
||||
mergeable: boolean | null;
|
||||
@@ -65,6 +78,7 @@ interface RawPR {
|
||||
export interface AugmentedPR extends RawPR {
|
||||
approvals_count: number;
|
||||
stale_state: StaleState;
|
||||
ci_status: CIStatus;
|
||||
priority_rank: number; // 0 = CI Blocker (highest), higher = lower; PRIORITY_NONE if unlabelled
|
||||
priority_label: string | null; // matched Priority/* label name, or null if none
|
||||
}
|
||||
@@ -80,6 +94,10 @@ interface BranchInfo {
|
||||
commit: { id: string };
|
||||
}
|
||||
|
||||
interface CombinedStatus {
|
||||
state: string; // 'success' | 'pending' | 'failure' | 'error' | 'warning' | '' | 'unknown'
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CONSTANTS
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -271,6 +289,17 @@ function computeStaleState(
|
||||
return 'stale_unknown';
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// mapCIState — translate Forgejo's combined commit-status state string into
|
||||
// our canonical CIStatus enum value
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
function mapCIState(state: string): CIStatus {
|
||||
if (state === 'success') return 'passing';
|
||||
if (state === 'pending') return 'pending';
|
||||
if (state === 'failure' || state === 'error' || state === 'warning') return 'failing';
|
||||
return 'unknown'; // '' | 'unknown' | anything else → no checks reported
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// listPRs — fetch, enrich, filter, and return matching PRs
|
||||
//
|
||||
@@ -347,12 +376,34 @@ export async function listPRs(
|
||||
REVIEW_CONCURRENCY,
|
||||
);
|
||||
|
||||
// ── Step 4: Compute fields, apply filters, collect results ────────────────
|
||||
// ── Step 4: Fetch CI (combined commit-status) for every PR concurrently ───
|
||||
// Uses GET /api/v1/repos/{owner}/{repo}/commits/{sha}/status which returns
|
||||
// the aggregated combined state for all status checks on the head commit.
|
||||
log(`Fetching CI status for ${rawPRs.length} PR(s) concurrently (limit=${REVIEW_CONCURRENCY})...`);
|
||||
|
||||
const allCIStatuses = await pLimit<CIStatus>(
|
||||
rawPRs.map(pr => async () => {
|
||||
try {
|
||||
const combined = await apiGet<CombinedStatus>(
|
||||
`${repoBase}/commits/${encodeURIComponent(pr.head.sha)}/status`,
|
||||
config.pat,
|
||||
);
|
||||
return mapCIState(combined.state ?? '');
|
||||
} catch {
|
||||
log(`WARNING: Could not fetch CI status for PR #${pr.number}; treating as 'unknown'`);
|
||||
return 'unknown' as CIStatus;
|
||||
}
|
||||
}),
|
||||
REVIEW_CONCURRENCY,
|
||||
);
|
||||
|
||||
// ── Step 5: Compute fields, apply filters, collect results ────────────────
|
||||
const results: AugmentedPR[] = [];
|
||||
|
||||
for (let i = 0; i < rawPRs.length; i++) {
|
||||
const pr = rawPRs[i];
|
||||
const approvalsCount = countApprovals(allReviews[i]);
|
||||
const ciStatus = allCIStatuses[i];
|
||||
const baseHead = branchCache.get(pr.base?.ref ?? '') ?? null;
|
||||
const staleState = computeStaleState(
|
||||
pr.merge_base ?? null,
|
||||
@@ -365,12 +416,14 @@ export async function listPRs(
|
||||
if (filters.maxApprovals !== undefined && approvalsCount > filters.maxApprovals) continue;
|
||||
if (filters.mergeable !== undefined && pr.mergeable !== filters.mergeable) continue;
|
||||
if (filters.stale !== undefined && staleState !== filters.stale) continue;
|
||||
if (filters.ciStatus !== undefined && !filters.ciStatus.includes(ciStatus)) continue;
|
||||
|
||||
const { rank: priorityRank, label: priorityLabel } = prPriority(pr);
|
||||
results.push({
|
||||
...pr,
|
||||
approvals_count: approvalsCount,
|
||||
stale_state: staleState,
|
||||
ci_status: ciStatus,
|
||||
priority_rank: priorityRank,
|
||||
priority_label: priorityLabel,
|
||||
});
|
||||
@@ -444,11 +497,12 @@ function parseFullArgs(argv: string[]): { config: Config; filters: Filters } {
|
||||
case '--pat': config.pat = val; i++; break;
|
||||
case '--owner': config.owner = val; i++; break;
|
||||
case '--repo': config.repo = val; i++; break;
|
||||
case '--state': filters.state = val as Filters['state']; i++; break;
|
||||
case '--min-approvals': filters.minApprovals = Number(val); i++; break;
|
||||
case '--max-approvals': filters.maxApprovals = Number(val); i++; break;
|
||||
case '--mergeable': filters.mergeable = val === 'true'; i++; break;
|
||||
case '--stale': filters.stale = val as StaleState; i++; break;
|
||||
case '--state': filters.state = val as Filters['state']; i++; break;
|
||||
case '--min-approvals': filters.minApprovals = Number(val); i++; break;
|
||||
case '--max-approvals': filters.maxApprovals = Number(val); i++; break;
|
||||
case '--mergeable': filters.mergeable = val === 'true'; i++; break;
|
||||
case '--stale': filters.stale = val as StaleState; i++; break;
|
||||
case '--ci-status': filters.ciStatus = val.split(',').map(s => s.trim()) as CIStatus[]; i++; break;
|
||||
case '-h':
|
||||
case '--help':
|
||||
printUsage();
|
||||
@@ -473,6 +527,7 @@ function printUsage(): void {
|
||||
process.stderr.write(`\
|
||||
Usage:
|
||||
list_prs.ts --url URL --pat TOKEN --owner OWNER --repo REPO [OPTIONS]
|
||||
[--ci-status passing|failing|pending|unknown[,...]]
|
||||
|
||||
REQUIRED:
|
||||
--url URL Forgejo base URL, no trailing slash (e.g. https://git.example.com)
|
||||
@@ -487,11 +542,20 @@ OPTIONAL FILTERS (all default to no filter):
|
||||
--mergeable true|false Filter on PR.mergeable field
|
||||
--stale not_stale|stale_no_conflicts|stale_with_conflicts|stale_unknown
|
||||
Filter on computed staleness state
|
||||
--ci-status passing|failing|pending|unknown[,...]
|
||||
Comma-separated whitelist of CI statuses to include.
|
||||
passing = all checks succeeded (Forgejo state: success)
|
||||
failing = at least one check failed/errored (failure|error|warning)
|
||||
pending = checks are queued or running (state: pending)
|
||||
unknown = no checks reported yet (state: ''|unknown)
|
||||
Multiple values: --ci-status passing,pending
|
||||
Omit flag entirely to include all CI statuses.
|
||||
|
||||
OUTPUT (stdout): JSON array sorted by priority (highest first); each object is
|
||||
the full Forgejo PR plus:
|
||||
.approvals_count integer distinct non-dismissed APPROVED review count
|
||||
.stale_state string one of the stale states listed above
|
||||
.ci_status string passing|failing|pending|unknown (see --ci-status above)
|
||||
.priority_rank integer 0=CI Blocker, 1=Critical, 2=High, 3=Medium,
|
||||
4=Low, 5=Backlog, 6=none (unlabelled)
|
||||
.priority_label string|null matched Priority/* label name, or null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env -S npx --yes tsx
|
||||
// =============================================================================
|
||||
// list_prs_ready_to_merge.ts — Open PRs approved and up-to-date with base
|
||||
// list_prs_ready_to_merge.ts — Open PRs approved, up-to-date, and CI passing
|
||||
//
|
||||
// Part of: .opencode/skills/auto-agents-system/scripts/
|
||||
//
|
||||
@@ -9,15 +9,22 @@
|
||||
// Returns all OPEN pull requests that:
|
||||
// • have at least 1 review approval (approvals_count >= 1)
|
||||
// • are NOT stale (stale_state == not_stale)
|
||||
// • have passing CI (ci_status == 'passing')
|
||||
//
|
||||
// These PRs are ready to be merged immediately — they are approved and their
|
||||
// branch is already up-to-date with the base branch, so no rebase or conflict
|
||||
// resolution is required before merging.
|
||||
// These PRs are ready to be merged immediately — they are approved, their
|
||||
// branch is already up-to-date with the base branch, and all CI checks have
|
||||
// passed. No rebase, conflict resolution, or CI wait is required.
|
||||
//
|
||||
// CI filter rationale: merging a PR with failing or pending CI would push
|
||||
// broken code to the base branch. Only 'passing' is accepted here. PRs
|
||||
// with 'pending' or 'failing' CI appear in none of the approved buckets
|
||||
// until their CI resolves, preventing premature merges.
|
||||
//
|
||||
// Hard-coded filters (not configurable):
|
||||
// state: 'open' only open PRs are considered
|
||||
// minApprovals: 1 at least one non-dismissed approval required
|
||||
// stale: 'not_stale' PR head must be current with its base branch
|
||||
// state: 'open' only open PRs are considered
|
||||
// minApprovals: 1 at least one non-dismissed approval required
|
||||
// stale: 'not_stale' PR head must be current with its base branch
|
||||
// ciStatus: ['passing'] all CI checks must have succeeded
|
||||
//
|
||||
// USAGE:
|
||||
// list_prs_ready_to_merge.ts --url URL --pat TOKEN --owner OWNER --repo REPO
|
||||
@@ -33,6 +40,7 @@ async function main(): Promise<void> {
|
||||
state: 'open',
|
||||
minApprovals: 1,
|
||||
stale: 'not_stale',
|
||||
ciStatus: ['passing'],
|
||||
});
|
||||
process.stdout.write(JSON.stringify(prs, null, 2) + '\n');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user