feat: add pr edit, close, and reopen (task 0011) #11

Merged
alexion merged 1 commits from task-0011-pr-edit-close-reopen into main 2026-07-13 20:09:47 -04:00
Owner

Implements .claude/tasks/0011-pr-edit-close-reopen.md.

Summary

Adds the PR-side state mutations, mirroring the issue-side slice with the two PR-specific differences:

  • pr edit <n> — applies --title, --body/--body-file, --base, --milestone (name-resolved), and the recomputed assignee list in a single PATCH; --add-label/--remove-label use Gitea's dedicated additive/removal endpoints (unapplied-label 404 is silent success); --add-reviewer/--remove-reviewer go through the dedicated requested-reviewers POST/DELETE endpoints with { reviewers: [...] } (ADR 0007 amendment — EditPullRequestOption has no reviewers field). Output edited: { number, status: "ok" }.
  • pr close <n> [--comment] — PATCHes state: "closed", then posts the comment as a second call whose failure is surfaced, not swallowed. An already-closed or merged PR is an already: true no-op reporting the actual state (merged for a merged PR).
  • pr reopen <n>already: true no-op when already open, else reopened: { number, status: "ok" }.

Extracts the fetch-then-patch assignee merge into a shared src/assignees.ts (mergeAssignees + assigneeLogins), now used by both issue edit and pr edit.

Three fixture-server test files cover edit (labels, assignees, reviewer add/remove, no-change guard), the merged-PR close no-op and close-comment partial failure, and the reopen paths. Full suite: 277 tests pass.

Deviations

pr reopen intentionally has no merged-guard (per spec): reopening a merged PR falls through to the PATCH and surfaces Gitea's rejection.

Review

Risk

Overall: Medium

  • Blast radius: Medium — three new pr subcommands plus a shared mergeAssignees refactored out of issue.ts, touching issue-edit callers too.
  • Reversibility: Low — purely additive CLI plus a comment-only refactor; trivial to revert, no schema/migration/published-API change.
  • Test coverage: Low — three new test files cover close, reopen, edit, no-op guards, help, label/reviewer/assignee flows, and error surfacing.
  • Sensitive domain: Medium — permission-adjacent PR mutations against a live Gitea API, though no auth/credentials logic changes.
  • Size & complexity: Medium — ~250 lines of new command logic with multi-endpoint orchestration and branching, but control flow stays linear.
  • Runtime criticality: Medium — user-facing CLI that mutates real PR state, though not a production hot path.

Unaddressed findings

Standards (both judgement calls):

  • Duplicated Code — the close/reopen state-machine (read state → no-op short-circuit → PATCH {state} → render) is still duplicated between issue.ts and pr.ts. Left unextracted because the two sides diverge in no-op shape, help suggestions, and the PR-only merged handling, which would make a shared helper leaky.
  • Divergent no-op shape — the issue side reports message: "Already closed" while the PR side reports already: true + state. Spec-driven (the spec fixes already: true for PRs), so not aligned here; the CLI's no-op output is not uniform across the two entities.

Spec: no violations.

Implements `.claude/tasks/0011-pr-edit-close-reopen.md`. ## Summary Adds the PR-side state mutations, mirroring the issue-side slice with the two PR-specific differences: - **`pr edit <n>`** — applies `--title`, `--body`/`--body-file`, `--base`, `--milestone` (name-resolved), and the recomputed assignee list in a single PATCH; `--add-label`/`--remove-label` use Gitea's dedicated additive/removal endpoints (unapplied-label 404 is silent success); `--add-reviewer`/`--remove-reviewer` go through the dedicated requested-reviewers POST/DELETE endpoints with `{ reviewers: [...] }` (ADR 0007 amendment — `EditPullRequestOption` has no reviewers field). Output `edited: { number, status: "ok" }`. - **`pr close <n> [--comment]`** — PATCHes `state: "closed"`, then posts the comment as a second call whose failure is surfaced, not swallowed. An already-closed or merged PR is an `already: true` no-op reporting the actual state (`merged` for a merged PR). - **`pr reopen <n>`** — `already: true` no-op when already open, else `reopened: { number, status: "ok" }`. Extracts the fetch-then-patch assignee merge into a shared `src/assignees.ts` (`mergeAssignees` + `assigneeLogins`), now used by both `issue edit` and `pr edit`. Three fixture-server test files cover edit (labels, assignees, reviewer add/remove, no-change guard), the merged-PR close no-op and close-comment partial failure, and the reopen paths. Full suite: 277 tests pass. ### Deviations `pr reopen` intentionally has no merged-guard (per spec): reopening a merged PR falls through to the PATCH and surfaces Gitea's rejection. ## Review ### Risk **Overall: Medium** - Blast radius: Medium — three new `pr` subcommands plus a shared `mergeAssignees` refactored out of `issue.ts`, touching issue-edit callers too. - Reversibility: Low — purely additive CLI plus a comment-only refactor; trivial to revert, no schema/migration/published-API change. - Test coverage: Low — three new test files cover close, reopen, edit, no-op guards, help, label/reviewer/assignee flows, and error surfacing. - Sensitive domain: Medium — permission-adjacent PR mutations against a live Gitea API, though no auth/credentials logic changes. - Size & complexity: Medium — ~250 lines of new command logic with multi-endpoint orchestration and branching, but control flow stays linear. - Runtime criticality: Medium — user-facing CLI that mutates real PR state, though not a production hot path. ### Unaddressed findings **Standards (both judgement calls):** - Duplicated Code — the close/reopen state-machine (read state → no-op short-circuit → PATCH `{state}` → render) is still duplicated between `issue.ts` and `pr.ts`. Left unextracted because the two sides diverge in no-op shape, help suggestions, and the PR-only merged handling, which would make a shared helper leaky. - Divergent no-op shape — the issue side reports `message: "Already closed"` while the PR side reports `already: true` + `state`. Spec-driven (the spec fixes `already: true` for PRs), so not aligned here; the CLI's no-op output is not uniform across the two entities. **Spec:** no violations.
alexion added 1 commit 2026-07-13 19:55:30 -04:00
feat: add pr edit, close, and reopen (task 0011)
All checks were successful
CI / test (pull_request) Successful in 38s
CI / test (push) Successful in 39s
965ece306f
Add the PR-side state mutations mirroring the issue-side slice:

- `pr edit` applies title/body/base/milestone and the recomputed assignee
  list in one PATCH, with additive label endpoints and (per the ADR 0007
  amendment) the dedicated requested-reviewers POST/DELETE endpoints for
  `--add-reviewer`/`--remove-reviewer`.
- `pr close --comment` posts the comment after the PATCH and surfaces a
  comment-post failure; an already-closed or merged PR is an `already: true`
  no-op reporting the actual state.
- `pr reopen` is an `already: true` no-op when already open.

Extract the fetch-then-patch assignee merge into a shared `src/assignees.ts`
(`mergeAssignees` + `assigneeLogins`), now used by both `issue edit` and
`pr edit`.
alexion merged commit 965ece306f into main 2026-07-13 20:09:47 -04:00
alexion deleted branch task-0011-pr-edit-close-reopen 2026-07-13 20:09:47 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: alexion/gitea-axi#11