Files
gitea-axi/.claude/tasks/0008-pr-list.md
alexion 6333058b72
All checks were successful
CI / test (pull_request) Successful in 30s
CI / test (push) Successful in 31s
feat: add pr list (task 0008)
Implements `pr list` with the two policies later PR slices reuse:
client-side filtering with the filtered-set count line (ADR 0005) and
the official-first reviewDecision via parallel per-PR review fetches
(ADR 0006), extracted into src/review.ts.

API-supported flags map to their params (--state, --author→poster,
--label name→ID, --label-id, --sort, --limit, --fields); --assignee,
--base, --head, and --draft filter in-process after full pagination,
with the count line's total taken from the filtered set. --search is
refused with a redirect to `search prs`.

Adds a boolText field extractor and a shared parsePositiveInt helper,
the latter also adopted by issue list's --limit parsing.
2026-07-12 19:23:29 -04:00

3.5 KiB

spec, blocked-by
spec blocked-by
gitea-axi 0004-issue-create-and-comment

What to build

pr list, introducing two policies that later slices reuse: client-side filtering with its count-line rule (see ADR 0005) and the reviewDecision computation via parallel review fetches (see ADR 0006). API-supported flags: --state, --author (maps to poster), --label (name→ID via the case-insensitive label lookup, since the PR list endpoint takes label IDs), --label-id (Gitea-specific bypass), --sort (Gitea-specific values passed straight to the API), --limit, --fields. Client-side filters (no API param exists): --assignee, --base, --head, --draft — each paginates fully at 50 per page and filters in-process, with the count line's T computed from the filtered set instead of the misleading X-Total-Count. Default fields: number, title, state, author, draft (bool→yes/no), review — the reviewDecision mapped to approved/changes_requested/required. reviewDecision uses the official-first fallback: only official reviews count when any exist, otherwise all reviews; CHANGES_REQUESTED beats APPROVED, non-stale non-dismissed approval wins, everything else is required; there is no none value. --search is forbidden with a redirect to search prs.

Acceptance criteria

  • pr list renders the default fields with review computed from one parallel review fetch per PR
  • reviewDecision honors the official-first fallback and maps to the three lowercase values, with zero-review and comment-only PRs rendering required
  • --label resolves the name case-insensitively to an ID (VALIDATION_ERROR if unknown); --label-id bypasses the lookup
  • --author and --sort map to their API params; --sort accepts the six Gitea values
  • --assignee, --base, --head, and --draft filter client-side after full pagination, and the count line reports count: N of T total with T from the in-memory filtered set
  • --fields exposes body, createdAt, labels, milestone, mergedAt, url
  • --search fails with VALIDATION_ERROR (exit 2) pointing at gitea-axi search prs "<query>"
  • Empty result emits pull_requests[0]: (none) plus a suggestion
  • Fixture-server tests cover the review computation variants (official/unofficial, stale, dismissed), each client-side filter with its count line, the label lookup, and the forbidden flag

Implementation Notes

  • The reviewDecision computation lives in a new src/review.ts module (reviewDecision pure core + fetchReviewDecision I/O shell), so pr view and the dashboard can reuse the same policy in later slices (ADR 0006).
  • Added a boolText field extractor to src/fields.ts for the draft bool→yes/no column; it is part of the shared field vocabulary rather than inlined, since pr view renders draft too.
  • Extracted parsePositiveInt into src/flags.ts and routed pr list's --limit/--label-id and issue list's --limit through it, collapsing three copies of the same positive-integer parse into one (a review finding). Behaviour and error wording are unchanged.
  • Small unrequested robustness kept deliberately: --label-id accepts a comma-separated list (mirroring --label), and --label + --label-id may be combined — their resolved IDs concatenate. The spec describes each as a single value; this is a strict superset with no behaviour change for the single-value case.
  • The url extra field plucks html_url (the browsable URL), matching issue list's precedent rather than Gitea's API url field.