Files
gitea-axi/.claude/CONTEXT.md
alexion d03a079854 docs: adopt Conventional Commits and land planning docs
- CONVENTIONAL-COMMITS.md: vendored spec (v1.0.0); CLAUDE.md requires
  agent-written commits to follow it
- tasks 0002-0020: remaining task breakdown of the gitea-axi spec
  (0019 reframed around the three-tier test taxonomy: unit,
  integration, end-to-end)
- spec, CONTEXT.md, and ADRs 0006/0007/0011: pending design
  refinements from the planning sessions
2026-07-11 07:14:39 -04:00

14 KiB

gitea-axi

A thin TypeScript CLI that calls the Gitea REST API directly via gitea-js to give coding agents an ergonomic, low-token interface to Gitea issues and pull requests.

Language

The tool and its host

gitea-axi: The CLI tool defined by this project. Avoid: wrapper, adapter, shim

tea: The official Gitea CLI whose login store gitea-axi reads for credential discovery; not used for command dispatch. Avoid: Gitea CLI, upstream binary

gitea-js: The official TypeScript client for the Gitea REST API, generated from Gitea's OpenAPI spec; the sole HTTP layer in gitea-axi. Avoid: API client, HTTP client, fetch wrapper

AXI (Agent eXperience Interface): The set of 10 design principles that govern how gitea-axi shapes its output and behavior for coding agents. Avoid: agent interface, UX principles

axi-sdk-js: The shared TypeScript framework package (axi-sdk-js on npm) that provides runAxiCli, AxiError, exitCodeForError, and output helpers; gitea-axi is built on it, matching gh-axi's architecture. Avoid: AXI library, SDK

Output

TOON: The structured text output format used for all gitea-axi output, encoded via @toon-format/toon. Avoid: JSON output, structured output

renderList: The output helper that formats a collection of entities as a TOON list, preceded by a count line. Avoid: list formatter, table renderer

dashboard: The output of gitea-axi with no arguments; a two-tier home view preceded by the bin: + description: header from axi-sdk-js. The short tier (no flags, and what the SessionStart hook runs) matches gh-axi's home shape: up to 3 open issues (number, title, state, author) and up to 3 open PRs (number, title, author, review), plus a help: hint pointing at --full. The full tier (gitea-axi --full) shows open PRs as a TOON table and open issue counts grouped by label. Issue counts are aggregated by fetching all open issues up to a hard cap of 1000 (page size 50, 20 pages max); if capped, the count is suffixed with +. Each issue contributes to all of its labels; unlabeled issues appear as a separate unlabeled row only when non-zero. Full-tier PR table default fields: number, title, author (plucked from user.login), labels (joined label names), review (computed client-side, same parallel review fetch as pr list). The full-tier PR table is capped at 20 rows with a standard count line (count: 20 of T total). Block names: repo: line, then prs: and issues:. Empty states (both tiers): prs: 0 open / issues: 0 open (raw strings, matching gh-axi's home; list commands keep <noun>[0]: (none)). Outside a recognizable Gitea repo the dashboard errors with REPO_NOT_FOUND (help: use -R + --login) — login selection needs a hostname; the resulting hook noise in non-Gitea sessions is an accepted consequence. Avoid: home view, status view

renderDetail: The output helper that formats a single entity's full detail as a TOON record. Avoid: detail formatter, record renderer

action-block/entity-block pattern: The uniform convention for mutation output — an action-named block (created:, edited:, closed:, reopened:, merged:) when the mutation actually ran, an entity-named block (issue:, pull_request:) when it was an idempotent no-op. Applies across issue and PR mutations alike; a deliberate departure from gh-axi, which returns entity blocks for issue-side mutation successes. Avoid: status block, result block

count line: The leading line in list output that states how many results were returned and their relationship to the total, e.g. count: N of T total. When a client-side filter is active, T is the true filtered total computed from the in-memory result set (the X-Total-Count header, which reflects the unfiltered total, is ignored); the bare count: N form does not exist. Avoid: summary line, header

FieldDef: A typed descriptor that extracts and formats a single field from raw Gitea API JSON, with named extractor variants (nested pluck, array join, enum map, bool-to-text, relative time). Avoid: field extractor, field descriptor

content truncation: Shortening body or diff text to a defined character limit and appending an inline hint — "... (truncated, N chars total - use --full to see complete body)" — directly into the field value. The full content is never written to a temp file; --full on the relevant subcommand suppresses all truncation in that command's output (entity body and comment bodies alike) and returns raw values instead. Comment bodies truncate at 800 chars wherever they appear (comment-post output and --comments view blocks), with cleanBody applied; --comments renders all comments with no count cap, matching gh-axi. Avoid: truncation, clipping, temp-file approach

cleanBody: A preprocessing step applied to body text before truncation, to reduce token cost. Applied only when the raw body exceeds the truncation limit. Normalizes Gitea issue/PR URLs (using the detected hostname) to compact form: https://<host>/<owner>/<repo>/issues/NIssue#N, https://<host>/<owner>/<repo>/pulls/NPR#N. Also strips markdown image embeds, long URLs in markdown links, standalone long URLs, and collapses email-style quoted blocks — matching gh-axi's cleanBody transformations. Avoid: body cleaning, URL normalization

Errors and suggestions

AxiError: The typed error value with one of ten named codes that gitea-axi emits on failure (TOON-encoded to stdout). The codes: REPO_NOT_FOUND, ISSUE_NOT_FOUND, PR_NOT_FOUND, AUTH_REQUIRED, FORBIDDEN, RATE_LIMITED, TEA_NOT_INSTALLED, VALIDATION_ERROR, GIT_ERROR, UNKNOWN. GIT_ERROR classifies non-zero git subprocess exits (currently only pr checkout), carrying git's first stderr line — the agent's recovery is local (fix the worktree), unlike API errors. The ISSUE_NOT_FOUND/PR_NOT_FOUND split (vs gh-axi's single NOT_FOUND) is a deliberate divergence enabled by path-based 404 classification; RATE_LIMITED maps HTTP 429 from proxies in front of Gitea. Avoid: error object, exception

next-step suggestion: A semi-dynamic hint appended to command output that tells the agent what to call next, normalized to include the current repo context flags. Rendered as a help[N]: block — the same block name used for error suggestions, matching gh-axi and canonical AXI Principle 9. Runtime values are hybrid: list output keeps placeholders (issue view <number>), single-entity output fills the actual id (issue view 42), matching canonical Principle 9 ("leave runtime values parameterized") and gh-axi. Avoid: hint, tip, recommendation, next[]

suggestion normalization: The process of rewriting a next-step suggestion to include -R OWNER/NAME and --login flags derived from the current repository context. Only applied when the context did not come from the git remote (i.e., when source is "flag" or "env"). Avoid: flag injection, context enrichment

Commands

issue blocks: A Gitea-specific subcommand group for managing which issues this issue blocks. Three sub-operations: list <n> (issues blocked by n), add <n> <target> (make n block target), remove <n> <target>. Idempotent: add of an existing relationship returns already: true (fetch-first check); remove of a nonexistent relationship is silent success; true validation failures (self-reference, cycles) still surface as VALIDATION_ERROR. No gh-axi equivalent — Gitea-specific API (/issues/{index}/blocks). Avoid: blocking, blocks list

issue blocked-by: A Gitea-specific subcommand group for managing which issues block this issue (i.e., must be resolved before this one). Three sub-operations: list <n>, add <n> <blocker>, remove <n> <blocker>. Same idempotency rules as issue blocks. No gh-axi equivalent — Gitea-specific API (/issues/{index}/dependencies). Avoid: depends, depends-on, dependencies

search: The full-text query commands (search issues <query>, search prs <query>), repo-scoped via owner param plus client-side filtering by repository (Gitea's /repos/issues/search has no repo-name filter). Results use a locator schema (number, title, state, author, created) — search finds the number; issue view / pr view load the detail. The forbidden --search flag on the list commands redirects here. Avoid: query command, find

Gitea API patterns

type guard: The defense against Gitea's unified issue/PR model, where issue endpoints also serve PRs. Every issues-list call passes type=issues (issue list, dashboard aggregation, client-side-filter pagination). Issue commands invoked with a PR number refuse with VALIDATION_ERROR ("issue #N is a pull request") and a pr view help line, detected via the fetched object's non-null pull_request field. Exception: issue comment stays permissive — PRs genuinely share the comment endpoint. Avoid: PR filtering, issue-only mode

reviewDecision: A computed field (not returned by Gitea) that summarizes the overall review state of a PR. Derived client-side from the reviews list with an official-first fallback: if any review is official=true, only official reviews are considered; otherwise all reviews are (unprotected repos never produce official reviews). Within the considered set: CHANGES_REQUESTED if any non-dismissed REQUEST_CHANGES; APPROVED if any non-dismissed, non-stale review with state APPROVED; otherwise REVIEW_REQUIRED (rendered required — covers zero-review and comment-only PRs; there is no none value). On pr list, this requires one extra parallel HTTP call per PR to fetch reviews. Avoid: review status, review aggregate

commit status: Gitea's CI/CD state mechanism, attached to a commit SHA via GET /repos/{owner}/{repo}/commits/{sha}/status. The state is one of pending, success, error, failure, warning, skipped (skipped exists in modern Gitea; older instances never emit it). gitea-axi uses this as the equivalent of GitHub Check Runs for pr checks and the checks field on pr view. Conclusion mapping: successpass; failure/error/warningfail (matching Gitea's own Combine() logic, which treats warning as failure); skippedskip; pendingpending. Avoid: check run, CI status, pipeline status

fetch-then-patch: The pattern used for additive or subtractive mutations on list fields where Gitea's PATCH replaces the entire list rather than adding/removing individual entries — applies to assignees only. gitea-axi reads the current list first, computes the desired list, then sends a single PATCH with the full resulting list. Reviewers do not use this pattern: EditPullRequestOption has no reviewers field; reviewer mutations go through the dedicated POST/DELETE /pulls/{index}/requested_reviewers endpoints (see ADR 0007 amendment). Avoid: read-modify-write, merge-then-patch

client-side filtering: The policy applied when Gitea's API does not support a given filter parameter. gitea-axi paginates all results from the API (using limit=50 pages until exhausted) and filters the full result set in-process. When any client-side filter is active, the count line emits count: N of T total with T computed from the in-memory filtered set (the unfiltered X-Total-Count header is ignored as misleading). Client-side sort (issue list --sort) is not a filter: it reorders without changing membership, so T comes from the X-Total-Count header as usual, while still requiring full pagination before sorting. Avoid: in-memory filtering, local filtering

label name lookup: The process of resolving a --label <name> string to a Gitea label ID before calling endpoints that require numeric IDs (e.g. pr list --label; note issue list --label does not need it — the issue-list endpoint accepts label names directly). Implemented via GET /repos/{owner}/{repo}/labels; matched case-insensitively. --label-id <id> is a Gitea-specific shortcut flag that bypasses the lookup and passes the ID directly. Avoid: label resolution, name-to-ID mapping

Testing

fixture server: The local HTTP server used in tests, pointed to by GITEA_AXI_API_URL, that maps incoming request paths and methods to pre-recorded Gitea API JSON response files. Avoid: mock server, stub server, fake API

fixture: A pre-recorded Gitea API JSON response file stored in fixtures/ that the fixture server returns for a given request path and method. Avoid: snapshot, recording

test mode: Activated when GITEA_AXI_API_URL is set. In test mode: all API calls go to the fixture server; tea subprocess is bypassed (token read from GITEA_AXI_TOKEN); git remote detection is suppressed. Three env vars together make tests fully hermetic: GITEA_AXI_API_URL, GITEA_AXI_TOKEN, GITEA_AXI_REPO. GITEA_AXI_REPO and GITEA_AXI_LOGIN are not test-mode-specific — they are general context overrides (priority: flag > env > git remote / hostname match, mirroring gh-axi's GH_REPO); test mode merely relies on them. Avoid: mock mode, stub mode

Distribution

Agent Skill: The markdown file bundled inside the npm package and installed to ~/.claude/skills/ by the setup command. Avoid: skill file, Claude skill

setup: The explicit subcommand that installs the Agent Skill into ~/.claude/skills/; gitea-axi's primary fulfillment of AXI Principle 7 (Ambient context). Idempotent: re-running reports already-installed/updated rather than failing. There is no postinstall script — installation of the skill is always an explicit user action. setup hooks additionally opts into the SessionStart hook. Avoid: postinstall, installer script

SessionStart hook: An opt-in ambient-context mechanism installed by setup hooks via axi-sdk-js's installSessionStartHooks() (Claude Code settings.json, Codex hooks.json, OpenCode plugin). It runs the bare gitea-axi binary (the short dashboard tier) in the session's working directory at session start and injects the output into the agent's context. The SDK's installer registers the binary with no arguments, so the hook always runs the short tier; outside a Gitea repo it produces the dashboard's REPO_NOT_FOUND error, an accepted noise trade-off. Avoid: session hook, ambient hook, postinstall hook