Files
gitea-axi/.claude/tasks/0005-issue-edit-close-reopen.md
alexion 6296f47fab
All checks were successful
CI / test (pull_request) Successful in 27s
CI / test (push) Successful in 37s
feat: add issue edit, close, and reopen (task 0005)
Implement the issue state-transition mutations:

- issue edit: --title, --body/--body-file, --add-label/--remove-label,
  --add-assignee/--remove-assignee, --milestone. Label mutations use
  Gitea's dedicated additive/removal endpoints (names added directly,
  removals resolved to IDs, unapplied-label 404 as silent success);
  assignees use fetch-then-patch (ADR 0007); title/body/milestone and the
  recomputed assignee list travel in one PATCH. Outputs edited: {number,
  status: ok}.
- issue close: PATCHes state closed, optional --comment posted after with
  its failure surfaced; already-closed short-circuits with Already closed.
- issue reopen: PATCHes state open; already-open short-circuits with
  Already open.

Extract a shared getIssue helper used by view/edit/close/reopen.
2026-07-12 09:47:51 -04:00

3.7 KiB

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

What to build

The issue state-transition mutations: issue edit, issue close, issue reopen. issue edit <n> supports --title, --body/--body-file, --add-label, --remove-label, --add-assignee, --remove-assignee, and --milestone (name-resolved). Label mutations use Gitea's dedicated additive/removal label endpoints: --add-label passes the name directly (no lookup); --remove-label resolves the name to an ID case-insensitively, erroring if the label does not exist in the repo, and treating Gitea's 404 for a label not applied to the issue as silent success. Assignee mutations introduce fetch-then-patch: read the current assignee list, apply the change in-process, send the full resulting list in one PATCH (see ADR 0007). issue close <n> PATCHes state: "closed", with optional --comment as a second API call whose failure is surfaced rather than swallowed; --reason is excluded. issue reopen <n> PATCHes state: "open". All three use the action-block pattern on success (edited:/closed:/reopened: with { number, status: "ok" }) — a deliberate departure from gh-axi's entity block on issue edit — and close/reopen return early with an Already closed/Already open message when a no-op.

Acceptance criteria

  • issue edit applies title, body, and milestone changes and outputs edited: { number, status: "ok" }
  • --add-label posts the name directly to the additive label endpoint; --remove-label resolves the ID first, yields VALIDATION_ERROR for a name not in the repo, and treats a 404 for an unapplied label as silent success
  • --add-assignee/--remove-assignee use fetch-then-patch, sending the full resulting assignee list in a single PATCH
  • issue close <n> outputs closed: { number, status: "ok" }; with --comment the comment is posted after the close, and a comment-post failure surfaces as an error even though the issue is closed
  • issue close on an already-closed issue and issue reopen on an already-open issue return early with message: "Already closed" / message: "Already open" and exit 0
  • issue reopen <n> outputs reopened: { number, status: "ok" }
  • Fixture-server tests cover each mutation path, both idempotent no-ops, the unapplied-label silent success, and the close-comment partial failure

Implementation Notes

No criteria were dropped or altered; all seven are satisfied.

Decisions made mid-implementation:

  • The idempotent no-op for close/reopen renders an entity block — issue: { number, state, message } — mirroring gh-axi, since the spec's deliberate action-block departure is scoped to the success path only. Determining the no-op requires a GET on the issue first, which also supplies the state reported in that block.
  • --add-label/--remove-label/--add-assignee/--remove-assignee are repeatable, matching issue create's repeatable --label, rather than the single-valued form the spec text implies.
  • Added a VALIDATION_ERROR when issue edit is invoked with no changes (documented in --help). The spec never specified the no-change case; this is a small justified extension, not scope creep.
  • Title/body/milestone and the recomputed assignee list travel in a single PATCH; label mutations use Gitea's dedicated endpoints afterward. Name resolution (milestone, remove-label IDs) runs before any mutation so a typo is reported before a change lands.
  • Extracted a shared getIssue helper (review finding) now used by view/edit/close/reopen.

Follow-up worth flagging: issue close/reopen do not type-guard against a PR number (unlike issue view), consistent with the spec, which does not require it.