Add the remaining simple issue mutations. `issue delete` hard-deletes via
the DELETE endpoint and is deliberately not idempotent — a nonexistent
issue yields ISSUE_NOT_FOUND rather than reporting success. `issue pin`
and `issue unpin` read the current pin state (Gitea's pin_order field) and
short-circuit to an idempotent no-op with an Already pinned/unpinned
message when there is nothing to do.
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.
Finish the `issue list` flag surface left minimal by the tracer slice:
- Server-side filters `--label`, `--assignee`, `--author`, `--milestone`,
mapped to Gitea's `labels`, `assigned_by`, `created_by`, `milestones`.
- Client-side `--sort <created|updated|comments>`, always descending, over
the fully paginated set (ADR 0005); `--limit` caps the sorted result.
- `--fields` exposing body, closedAt, labels, milestone, updatedAt, url.
- `--search` refused with a VALIDATION_ERROR redirecting to `search issues`.
Exhaustive pagination lands as a shared `paginate.ts`, since ADR 0005 makes
it a policy later slices reuse; `lookup.ts` drops its hand-rolled copy of the
same loop. The helper carries the 20-page cap that copy encoded, matching the
1000-item ceiling Principle 8 sets.
`pr create` takes --title (required), --body/--body-file, --base, --head,
--assignee, --reviewer, repeatable name-resolved --label, and --milestone.
An omitted --head defaults to the current local branch; an omitted --base to
the repository's default branch. Before creating, an existing open PR for the
same base/head pair short-circuits to `pull_request: { number, url, already:
true }` rather than opening a duplicate; only an open PR does, since a closed
one's branches are free to be proposed again.
`pr comment <n>` posts through the shared issue-comment endpoint and returns
the created comment as `comment: { number, author, created, body }` (ADR 0008),
reporting a 404 as PR_NOT_FOUND since the caller asked about a pull request.
That comment block is now built in one place (src/comment.ts) for both issue
and pr comment, as ADR 0008 requires them to stay identical.
Introduce the first mutations, along with the shared machinery the later
issue and PR mutation slices reuse.
- `issue create` with --title/--body/--body-file/--assignee/--label/
--milestone/--fields, emitting `issue: { number, title, state, url }`
- `issue comment <n>`, echoing the created comment from the POST response
with the body cleaned and truncated at 800 chars
- body-source resolution (--body vs --body-file), label and milestone
name->ID lookup, repeatable flags, and the `joined`/`selectExtraFields`
field extractors
Label lookup pages until exhausted, since a repo with more labels than one
page would otherwise fail to resolve a valid name. The end-to-end tier seeds
a mixed-case label and milestone and passes both in a different case, so the
case-insensitive lookup is verified against live Gitea rather than only
against fixtures.
Add `issue view <n>` as the first detail command, introducing the
content-cleaning and truncation machinery (src/body.ts) that later issue
and PR slices reuse.
- Default output: number, title, state, author, created, body (truncated
at 500), plus comment_count; --comments expands every comment (bodies
truncated at 800); --full suppresses all truncation.
- cleanBody runs only when a body exceeds its limit: normalizes Gitea
issue/PR URLs on the detected host to Issue#N/PR#N, strips image embeds
and long URLs, and collapses quoted blocks.
- Type guard: a PR number fails with VALIDATION_ERROR and a `pr view <n>`
hint, detected via the fetched object's pull_request field.
- renderDetail joins the detail entity, optional sub-blocks, and help.
Cover the previously untested credential and error-classification paths:
- tea.ts: failing/invalid/non-array `tea login list`, a failing token
helper, and an empty token, all driven through the CLI seam via the fake
tea harness (now parametrizable for exit codes and output).
- errors.ts: a unit-tier test of classifyHttpError for the pure-classifier
branches no shipped command reaches yet (issue/pull 404s, the
already-classified passthrough, transport failures, no-body defaults).
errors.ts is now fully covered and tea.ts is at 96% (the remainder is
defensive code unreachable through the seam). Overall coverage rose from
~87% to ~93% statements / ~89% branches; the ratchet is raised to
92/87/95/92 accordingly.
Add the end-to-end test tier: a Gitea Actions workflow (GitHub-compatible)
that runs the unit+integration tiers plus e2e tests against a pinned
disposable Gitea service container. The e2e suite provisions its own repo,
token, and seed data over HTTP and drives the real CLI seam, gated on
GITEA_AXI_E2E_URL so the default suite stays dependency-free.
A fixture-vs-live shape guard anchors the issue-list FieldDef paths against
both the recorded fixture and the live response, failing on divergence.
Tracer bullet for gitea-axi: runnable npm package on axi-sdk-js with
gitea-js as the sole HTTP layer, ESM on Node 20+.
- issue list with --state/--limit, default fields, count line from
X-Total-Count, type=issues guard, explicit empty state, and next-step
suggestions
- repo context detection from the git origin remote (SSH/scp/HTTPS),
tea credential discovery with the three-way login-matching split, and
-R/--repo and --login overrides (flag > env > auto)
- token retrieval via tea login helper get: tea's login list JSON
carries no token (ADR 0001 amended)
- full AxiError classification table with path-based 404 split, TOON
errors on stdout, exit codes 0/1/2
- test mode (GITEA_AXI_API_URL/TOKEN/REPO) suppressing subprocesses,
fixture server, and vitest suites driving the CLI seam (50 tests)