# list_issues — Open Issues Excluding In-Progress, Sorted by Milestone then Priority

**Script name:** `list_issues`

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

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

Fetches all **open issues** from a Forgejo repository, filters out any that
carry the `State/In Progress` label, and returns them sorted by:

1. **Milestone due date** — ascending (oldest due date first); issues with no
   milestone or a milestone with no `due_on` sort last.
2. **Priority label** — ascending rank within each milestone tier (highest
   priority first): `Priority/CI Blocker` (0) → `Priority/Critical` (1)
   → `Priority/High` (2) → `Priority/Medium` (3) → `Priority/Low` (4)
   → `Priority/Backlog` (5) → unlabelled (6)

This script is **standalone** — it does not import from `list_prs.ts`. It
directly calls the Forgejo issues API (`GET /repos/{owner}/{repo}/issues`) and
fetches pages in parallel batches for performance.

Unlike the `list_prs_*` wrapper scripts, this script does not delegate to a
shared core; its full implementation lives in `list_issues.ts` itself.


## Arguments

All four arguments are **required**.

| 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>`.


## Filtering

**Excluded: `State/In Progress`** — any open issue carrying this label is
dropped before sorting. The label name is matched exactly. Issues carrying any
other `State/*` label (e.g. `State/Unverified`, `State/In Review`,
`State/Verified`, `State/Paused`) are **included**.

PRs are also excluded: the Forgejo API parameter `type=issues` restricts the
response to true issues only (pull requests have a separate endpoint).


## Sorting

The final output array is sorted by two keys applied in order:

### Key 1: `milestone_due_sort` (primary, ascending)

Issues are sorted by their milestone's `due_on` date (Unix ms timestamp,
ascending — oldest due date first). Issues with no milestone or a milestone
with no `due_on` set are assigned `Number.MAX_SAFE_INTEGER` and sort last.

This means milestones with a concrete `due_on` date all appear before milestones
without one (e.g. `v3.7.0`, `v3.8.0`) and before truly unmilestoned issues.
Milestones that share the same `due_on` timestamp are contiguous and their
relative order is determined by the secondary key.

### Key 2: `priority_rank` (secondary, ascending)

Within each milestone tier (same `milestone_due_sort` value), issues are
sub-sorted by priority label:

| Rank | Label | Meaning |
|------|-------|---------|
| 0 | `Priority/CI Blocker` | Highest — blocks CI pipeline |
| 1 | `Priority/Critical` | |
| 2 | `Priority/High` | |
| 3 | `Priority/Medium` | |
| 4 | `Priority/Low` | |
| 5 | `Priority/Backlog` | |
| 6 | *(none)* | Lowest — no recognised Priority/* label |

When an issue carries multiple `Priority/*` labels, the highest-priority (lowest
rank number) label wins.

### Stability

JavaScript's `Array.sort` is stable. Issues that share the same
`milestone_due_sort` **and** `priority_rank` retain their original API order
(Forgejo returns issues creation-descending by default).


## Output

Written to **stdout** as a pretty-printed JSON array. Each element is the full
Forgejo issue object as returned by the API, augmented with three computed fields:

| Field | Type | Description |
|-------|------|-------------|
| `priority_rank` | integer | 0 (CI Blocker) through 6 (unlabelled) |
| `priority_label` | string \| null | Matched `Priority/*` label name, or `null` |
| `milestone_due_sort` | number | Unix ms timestamp of `milestone.due_on`; `Number.MAX_SAFE_INTEGER` if absent |

The `milestone_due_sort` field is a sort artifact — it is included so callers
can verify or re-sort without re-parsing the `milestone.due_on` string.

An **empty array** `[]` means all open issues are currently `State/In Progress`.

See the [Forgejo issues API](https://forgejo.org/docs/latest/developer/api-usage/)
for the full issue object schema. Notable fields: `number`, `title`, `body`,
`labels`, `milestone`, `assignees`, `created`, `updated`.


## Diagnostics / stderr

Progress messages go to stderr and do not affect the JSON output on stdout:

```
Fetching open issues from https://git.example.com/org/repo...
Fetched 6275 open issue(s).  Filtering and sorting...

Result: 6197/6275 issue(s) after filtering "State/In Progress".
```


## 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 any page | Exit 1 with HTTP status and body detail |
| Single page fetch failure in a batch | Warning suppressed; page treated as empty, processing continues |
| Network timeout (30 s per request) | Exit 1 with error message |


## Performance Notes

Pages are fetched in batches of 6 in parallel (same strategy as `list_prs.ts`).
With ~6,275 issues at 50 per page that is ~126 pages, requiring ~21 parallel
batch rounds. On a good connection this typically completes in under 30 seconds.
No secondary API calls are made (no review, CI, or branch lookups) — all
required data is embedded in the issue response.


## When to Use This Script

Use `list_issues` when you need a prioritised, actionable backlog of open
issues with in-progress work already excluded. Typical use cases:

- **`issue-dispatch-supervisor`** calls this to discover the next issue to
  assign to a worker agent. Because the output is sorted by priority then
  milestone due date, the supervisor can take `.[0]` (the first element) and
  be confident it is picking the highest-priority, most-overdue unstarted issue.
- A **sprint-planning agent** can slice the output by milestone title to see
  what work remains in a specific milestone, already sorted by priority.
- A **reporting agent** can group the output by `priority_label` and
  `milestone.title` to generate a burndown or triage summary.
- A **human liaison agent** can identify issues that are unlabelled (rank 6)
  or unmilestoned for human triage.

### Getting the Next Issue to Work On

```bash
# The first element is always the highest-priority, earliest-due-date issue
# that is not currently in progress — pick it up and start working.
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url $FORGEJO_URL --pat $FORGEJO_PAT --owner $REPO_OWNER --repo $REPO_NAME \
  | jq 'first'
```


## Usage Examples

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

# Count actionable open issues (not in progress)
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq 'length'

# Get the single highest-priority, most-overdue issue to work on next
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq 'first | {number, title, priority_label, milestone: .milestone.title}'

# List all Critical issues due in milestone v3.2.0
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq '[.[] | select(.priority_label == "Priority/Critical" and .milestone.title == "v3.2.0") | {number, title}]'

# Group by priority tier — see count per tier
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq 'group_by(.priority_label) | map({label: (.[0].priority_label // "none"), count: length})'

# Get issues with no milestone (need triaging)
npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url https://git.example.com \
  --pat ghp_XXXX \
  --owner my-org \
  --repo my-repo \
  | jq '[.[] | select(.milestone == null) | {number, title, priority_label}]'
```


## Agent Usage Examples

### issue-dispatch-supervisor

The supervisor calls this script at the start of each cycle to build its work
queue. It takes the first N results (highest priority, earliest due date first)
and dispatches `issue-worker` agents for each.

```bash
issues=$(npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url $FORGEJO_URL --pat $FORGEJO_PAT --owner $REPO_OWNER --repo $REPO_NAME)
# Take top 5 issues and dispatch workers
echo "$issues" | jq '.[0:5][]' | while read -r issue; do
  number=$(echo "$issue" | jq -r '.number')
  # dispatch issue-worker for issue $number
done
```

### sprint-planning-agent

A planning agent can use this to see the full prioritised backlog per milestone,
without needing to make additional API calls.

```bash
issues=$(npx --yes tsx /app/.opencode/skills/auto-agents-system/scripts/list_issues.ts \
  --url $FORGEJO_URL --pat $FORGEJO_PAT --owner $REPO_OWNER --repo $REPO_NAME)
# Summarise by milestone
echo "$issues" | jq 'group_by(.milestone.title // "none") | map({milestone: .[0].milestone.title, count: length, issues: map({number, title, priority_label})})'
```


## Dependencies

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