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
This commit is contained in:
@@ -44,6 +44,10 @@ _Avoid_: home view, status view
|
|||||||
**renderDetail**: The output helper that formats a single entity's full detail as a TOON record.
|
**renderDetail**: The output helper that formats a single entity's full detail as a TOON record.
|
||||||
_Avoid_: detail formatter, record renderer
|
_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`.
|
**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.
|
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
|
_Avoid_: summary line, header
|
||||||
@@ -93,6 +97,11 @@ Same idempotency rules as [[issue blocks]].
|
|||||||
No gh-axi equivalent — Gitea-specific API (`/issues/{index}/dependencies`).
|
No gh-axi equivalent — Gitea-specific API (`/issues/{index}/dependencies`).
|
||||||
_Avoid_: depends, depends-on, 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
|
### Gitea API patterns
|
||||||
|
|
||||||
**type guard**: The defense against Gitea's unified issue/PR model, where issue endpoints also serve PRs.
|
**type guard**: The defense against Gitea's unified issue/PR model, where issue endpoints also serve PRs.
|
||||||
@@ -102,7 +111,8 @@ Exception: `issue comment` stays permissive — PRs genuinely share the comment
|
|||||||
_Avoid_: PR filtering, issue-only mode
|
_Avoid_: PR filtering, issue-only mode
|
||||||
|
|
||||||
**reviewDecision**: A computed field (not returned by Gitea) that summarizes the overall review state of a PR.
|
**reviewDecision**: A computed field (not returned by Gitea) that summarizes the overall review state of a PR.
|
||||||
Derived client-side from the reviews list: `APPROVED` if at least one review has `official=true`, `stale=false`, `dismissed=false`, and no non-dismissed `REQUEST_CHANGES` review exists; `CHANGES_REQUESTED` if any non-dismissed `REQUEST_CHANGES` exists; otherwise `REVIEW_REQUIRED`.
|
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.
|
On `pr list`, this requires one extra parallel HTTP call per PR to fetch reviews.
|
||||||
_Avoid_: review status, review aggregate
|
_Avoid_: review status, review aggregate
|
||||||
|
|
||||||
@@ -112,8 +122,9 @@ gitea-axi uses this as the equivalent of GitHub Check Runs for `pr checks` and t
|
|||||||
Conclusion mapping: `success`→`pass`; `failure`/`error`/`warning`→`fail` (matching Gitea's own `Combine()` logic, which treats `warning` as failure); `skipped`→`skip`; `pending`→`pending`.
|
Conclusion mapping: `success`→`pass`; `failure`/`error`/`warning`→`fail` (matching Gitea's own `Combine()` logic, which treats `warning` as failure); `skipped`→`skip`; `pending`→`pending`.
|
||||||
_Avoid_: check run, CI status, pipeline status
|
_Avoid_: check run, CI status, pipeline status
|
||||||
|
|
||||||
**fetch-then-patch**: The pattern used for additive or subtractive mutations on list fields (assignees, reviewers) where Gitea's PATCH replaces the entire list rather than adding/removing individual entries.
|
**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.
|
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
|
_Avoid_: read-modify-write, merge-then-patch
|
||||||
|
|
||||||
**client-side filtering**: The policy applied when Gitea's API does not support a given filter parameter.
|
**client-side filtering**: The policy applied when Gitea's API does not support a given filter parameter.
|
||||||
@@ -122,7 +133,7 @@ When any client-side filter is active, the count line emits `count: N of T total
|
|||||||
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.
|
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
|
_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`, `issue list --label`).
|
**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.
|
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.
|
`--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
|
_Avoid_: label resolution, name-to-ID mapping
|
||||||
|
|||||||
@@ -23,3 +23,12 @@ Accepted explicitly: API call cost does not factor into design decisions for thi
|
|||||||
- `pr list` with N results makes N+1 HTTP calls (list + N review fetches).
|
- `pr list` with N results makes N+1 HTTP calls (list + N review fetches).
|
||||||
- `official` and `stale` fields are exposed on `pr view --reviews` as Gitea-specific bonus data.
|
- `official` and `stale` fields are exposed on `pr view --reviews` as Gitea-specific bonus data.
|
||||||
- The `reviewDecision` field appears in the default schema for both `pr list` and `pr view`.
|
- The `reviewDecision` field appears in the default schema for both `pr list` and `pr view`.
|
||||||
|
|
||||||
|
## Amendment (2026-07-10): official-first fallback
|
||||||
|
|
||||||
|
The original logic required `official=true` for `APPROVED`, but Gitea only marks reviews official under branch protection with required approvals.
|
||||||
|
On unprotected repos (the norm on personal instances) every review is `official=false`, making `APPROVED` unreachable — an approved PR would show `required` forever.
|
||||||
|
|
||||||
|
Amended logic: if any review on the PR carries `official=true`, consider only official reviews (branch-protection semantics preserved); otherwise consider all reviews.
|
||||||
|
Within the considered set the derivation is unchanged: `CHANGES_REQUESTED` if any non-dismissed `REQUEST_CHANGES`; `APPROVED` if any non-dismissed, non-stale review with state `APPROVED`; `REVIEW_REQUIRED` otherwise.
|
||||||
|
The otherwise-bucket (zero reviews, comment-only) renders `required`; gitea-axi deliberately has no `none` value, since detecting "review not formally required" would need admin-only branch-protection API access.
|
||||||
|
|||||||
@@ -27,3 +27,12 @@ Accepted cost: same policy as client-side filtering — extra HTTP calls do not
|
|||||||
- The operation is not atomic: a concurrent edit between the GET and the PATCH could cause a lost update.
|
- The operation is not atomic: a concurrent edit between the GET and the PATCH could cause a lost update.
|
||||||
Accepted as a known limitation for single-agent workflows.
|
Accepted as a known limitation for single-agent workflows.
|
||||||
- `issue label --add` / `--remove` does NOT use fetch-then-patch — Gitea has dedicated additive label endpoints that are already idempotent.
|
- `issue label --add` / `--remove` does NOT use fetch-then-patch — Gitea has dedicated additive label endpoints that are already idempotent.
|
||||||
|
|
||||||
|
## Amendment (2026-07-10): reviewers use dedicated endpoints, not fetch-then-patch
|
||||||
|
|
||||||
|
The original decision was wrong about reviewers on two counts:
|
||||||
|
`EditPullRequestOption` has no reviewers field at all (fetch-then-patch is impossible, not merely chosen against), and Gitea does have dedicated add/remove endpoints — `POST`/`DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers` with `{ reviewers: string[] }`.
|
||||||
|
|
||||||
|
`pr edit --add-reviewer` / `--remove-reviewer` therefore use the dedicated review-request endpoints, mirroring the label-mutation pattern.
|
||||||
|
Fetch-then-patch remains the pattern for assignees only (issues and PRs), where the PATCH body does replace the whole list and no dedicated endpoints exist.
|
||||||
|
`pr create --reviewer` is unaffected: `CreatePullRequestOption` accepts `reviewers` directly.
|
||||||
|
|||||||
@@ -17,3 +17,14 @@ One uniform code path, no remote mutation, no fork credentials.
|
|||||||
- Same-repo and fork PRs check out identically.
|
- Same-repo and fork PRs check out identically.
|
||||||
- Git subprocess failures (dirty worktree, network) classify as `GIT_ERROR`, carrying git's first stderr line.
|
- Git subprocess failures (dirty worktree, network) classify as `GIT_ERROR`, carrying git's first stderr line.
|
||||||
- The created local branch does not track the contributor's fork; pushing back to a fork branch is out of scope.
|
- The created local branch does not track the contributor's fork; pushing back to a fork branch is out of scope.
|
||||||
|
|
||||||
|
## Amendment (2026-07-10): three-case handling for existing local branches
|
||||||
|
|
||||||
|
The original two-step pipeline fails on re-checkout: git refuses to fetch into the currently checked-out branch, and a moved PR head makes the plain fetch non-fast-forward — so the second run of the same command errored, violating Principle 6.
|
||||||
|
|
||||||
|
Amended behavior:
|
||||||
|
1. Branch absent — original pipeline unchanged.
|
||||||
|
2. Branch exists, not checked out — force-fetch (`+pull/<n>/head:<branch>`) then checkout; the local branch is defined as a mirror of the PR head.
|
||||||
|
3. Branch currently checked out — fetch `pull/<n>/head` then `git merge --ff-only FETCH_HEAD`; divergence (local commits not on the PR head) surfaces as `GIT_ERROR` with explanatory help rather than being silently discarded.
|
||||||
|
|
||||||
|
A force-reset-always variant was rejected: fully idempotent but silently destroys local commits, which is unacceptable for unattended agents.
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ The short version: tea's create commands have no `--output json` flag, its PR li
|
|||||||
gitea-axi reads credentials from tea's login store via `tea login list --output json`.
|
gitea-axi reads credentials from tea's login store via `tea login list --output json`.
|
||||||
It requires `tea` to be installed and at least one login configured via `tea login add`.
|
It requires `tea` to be installed and at least one login configured via `tea login add`.
|
||||||
At startup, it detects the current repository's Gitea hostname (see Repository Context Detection), finds the matching login entry, and extracts the token for all subsequent API calls.
|
At startup, it detects the current repository's Gitea hostname (see Repository Context Detection), finds the matching login entry, and extracts the token for all subsequent API calls.
|
||||||
|
When multiple logins match the hostname: if tea's default login is among them it is used; otherwise `VALIDATION_ERROR` listing the matching profile names, with help to pass `--login <name>` — an arbitrary identity is never picked silently.
|
||||||
Tea is used only for credential discovery — no commands are dispatched through the tea subprocess.
|
Tea is used only for credential discovery — no commands are dispatched through the tea subprocess.
|
||||||
|
|
||||||
### Command Surface
|
### Command Surface
|
||||||
@@ -93,7 +94,7 @@ This holds even when invoked by the SessionStart hook; error noise in non-Gitea
|
|||||||
`--sort <created|updated|comments>` (client-side — Gitea issue list has no sort param; always descending, matching gh-axi);
|
`--sort <created|updated|comments>` (client-side — Gitea issue list has no sort param; always descending, matching gh-axi);
|
||||||
`--limit <n>` (default 30);
|
`--limit <n>` (default 30);
|
||||||
`--fields <a,b,c>`.
|
`--fields <a,b,c>`.
|
||||||
`--search` is explicitly forbidden (VALIDATION_ERROR).
|
`--search` is explicitly forbidden (VALIDATION_ERROR; help: `` Use `gitea-axi search issues "<query>"` for full-text search ``).
|
||||||
Always passes `type=issues` — Gitea's issue endpoints also serve PRs, which must never appear in issue lists (see Issue/PR Type Guard).
|
Always passes `type=issues` — Gitea's issue endpoints also serve PRs, which must never appear in issue lists (see Issue/PR Type Guard).
|
||||||
Client-side `--sort` reorders without changing membership, so the standard `count: N of T total` line is kept (see ADR 0005); full pagination still precedes sorting.
|
Client-side `--sort` reorders without changing membership, so the standard `count: N of T total` line is kept (see ADR 0005); full pagination still precedes sorting.
|
||||||
Default output fields (matching gh-axi): `number`, `title`, `state` (lowercased), `author` (plucked from `user.login`), `created` (relative time).
|
Default output fields (matching gh-axi): `number`, `title`, `state` (lowercased), `author` (plucked from `user.login`), `created` (relative time).
|
||||||
@@ -127,6 +128,7 @@ Extra fields available via `--fields`: `labels`, `assignees`, `milestone`, `body
|
|||||||
`--add-assignee <login>`;
|
`--add-assignee <login>`;
|
||||||
`--remove-assignee <login>`;
|
`--remove-assignee <login>`;
|
||||||
`--milestone <name>` (resolved to milestone ID via `GET /milestones?name=<name>` — `VALIDATION_ERROR` if not found).
|
`--milestone <name>` (resolved to milestone ID via `GET /milestones?name=<name>` — `VALIDATION_ERROR` if not found).
|
||||||
|
Output on success: `edited: { number, status: "ok" }` — the action-block/entity-block pattern (see `pr create`) applied uniformly across issue and PR mutations; a deliberate departure from gh-axi, whose `issue edit` returns the updated `issue:` entity block.
|
||||||
Label mutations use Gitea's dedicated additive/removal label endpoints (idempotent).
|
Label mutations use Gitea's dedicated additive/removal label endpoints (idempotent).
|
||||||
`--add-label` passes the name directly via `POST /issues/{index}/labels` (Gitea accepts names here — no lookup needed).
|
`--add-label` passes the name directly via `POST /issues/{index}/labels` (Gitea accepts names here — no lookup needed).
|
||||||
`--remove-label` requires an ID: resolved via case-insensitive label lookup; `VALIDATION_ERROR` if the label name does not exist in the repo; if the label exists but is not applied to this issue, Gitea's 404 on `DELETE /labels/{id}` is treated as silent success.
|
`--remove-label` requires an ID: resolved via case-insensitive label lookup; `VALIDATION_ERROR` if the label name does not exist in the repo; if the label exists but is not applied to this issue, Gitea's 404 on `DELETE /labels/{id}` is treated as silent success.
|
||||||
@@ -138,10 +140,12 @@ Closing sets `state: "closed"` via PATCH on the issue.
|
|||||||
`--reason` is excluded (Gitea has no `state_reason` concept).
|
`--reason` is excluded (Gitea has no `state_reason` concept).
|
||||||
When `--comment` is provided, two API calls are made: PATCH to close, then POST to create the comment.
|
When `--comment` is provided, two API calls are made: PATCH to close, then POST to create the comment.
|
||||||
If the PATCH succeeds but the POST fails, the error is surfaced — the issue remains closed but the failure is reported rather than silently swallowed.
|
If the PATCH succeeds but the POST fails, the error is surfaced — the issue remains closed but the failure is reported rather than silently swallowed.
|
||||||
|
Output on success: `closed: { number, status: "ok" }` (action-block/entity-block pattern).
|
||||||
Idempotent: returns early with `message: "Already closed"` if already closed.
|
Idempotent: returns early with `message: "Already closed"` if already closed.
|
||||||
|
|
||||||
**`issue reopen <n>`**
|
**`issue reopen <n>`**
|
||||||
Sets `state: "open"` via PATCH.
|
Sets `state: "open"` via PATCH.
|
||||||
|
Output on success: `reopened: { number, status: "ok" }` (action-block/entity-block pattern).
|
||||||
Idempotent: returns early with `message: "Already open"` if already open.
|
Idempotent: returns early with `message: "Already open"` if already open.
|
||||||
|
|
||||||
**`issue comment <n> [flags]`**
|
**`issue comment <n> [flags]`**
|
||||||
@@ -203,7 +207,7 @@ No gh-axi equivalent.
|
|||||||
`--sort <oldest|recentupdate|leastupdate|mostcomment|leastcomment|priority>` (Gitea-specific extension — maps directly to the API `sort` param);
|
`--sort <oldest|recentupdate|leastupdate|mostcomment|leastcomment|priority>` (Gitea-specific extension — maps directly to the API `sort` param);
|
||||||
`--limit <n>` (default 30);
|
`--limit <n>` (default 30);
|
||||||
`--fields <a,b,c>`.
|
`--fields <a,b,c>`.
|
||||||
`--search` is explicitly forbidden (VALIDATION_ERROR).
|
`--search` is explicitly forbidden (VALIDATION_ERROR; help: `` Use `gitea-axi search prs "<query>"` for full-text search ``).
|
||||||
Default output fields (matching gh-axi): `number`, `title`, `state` (lowercased), `author` (plucked from `user.login`), `draft` (bool→yes/no), `review` (`reviewDecision` mapped: APPROVED→approved, CHANGES_REQUESTED→changes_requested, REVIEW_REQUIRED→required).
|
Default output fields (matching gh-axi): `number`, `title`, `state` (lowercased), `author` (plucked from `user.login`), `draft` (bool→yes/no), `review` (`reviewDecision` mapped: APPROVED→approved, CHANGES_REQUESTED→changes_requested, REVIEW_REQUIRED→required).
|
||||||
Extra fields via `--fields`: `body` (raw), `createdAt` (relative time, as `created`), `labels` (joined names), `milestone` (title), `mergedAt` (relative time, as `merged_at`), `url`.
|
Extra fields via `--fields`: `body` (raw), `createdAt` (relative time, as `created`), `labels` (joined names), `milestone` (title), `mergedAt` (relative time, as `merged_at`), `url`.
|
||||||
`reviewDecision` is computed client-side by fetching reviews for each PR in parallel (one extra HTTP call per PR; see ADR 0006).
|
`reviewDecision` is computed client-side by fetching reviews for each PR in parallel (one extra HTTP call per PR; see ADR 0006).
|
||||||
@@ -250,11 +254,13 @@ Output on success: `created: { number, url }` — completing gh-axi's action-blo
|
|||||||
`--remove-reviewer <login>`;
|
`--remove-reviewer <login>`;
|
||||||
`--milestone <name>` (resolved to milestone ID via `GET /milestones?name=<name>` — `VALIDATION_ERROR` if not found);
|
`--milestone <name>` (resolved to milestone ID via `GET /milestones?name=<name>` — `VALIDATION_ERROR` if not found);
|
||||||
`--base <branch>`.
|
`--base <branch>`.
|
||||||
Assignee and reviewer mutations use fetch-then-patch (see ADR 0007).
|
Assignee mutations use fetch-then-patch (see ADR 0007).
|
||||||
|
Reviewer mutations use Gitea's dedicated review-request endpoints (`POST`/`DELETE /pulls/{index}/requested_reviewers` with `{ reviewers: [login] }`) — `EditPullRequestOption` has no reviewers field, so fetch-then-patch is structurally impossible for reviewers (see ADR 0007 amendment).
|
||||||
Output: `edited: { number, status: "ok" }`.
|
Output: `edited: { number, status: "ok" }`.
|
||||||
|
|
||||||
**`pr close <n> [flags]`**
|
**`pr close <n> [flags]`**
|
||||||
`--comment <text>`.
|
`--comment <text>`.
|
||||||
|
When `--comment` is provided, two API calls are made (PATCH to close, then POST the comment); if the PATCH succeeds but the POST fails, the error is surfaced — same partial-failure policy as `issue close`.
|
||||||
Idempotent: returns `pull_request: { number, state, already: true }` if already closed or merged.
|
Idempotent: returns `pull_request: { number, state, already: true }` if already closed or merged.
|
||||||
Output on success: `closed: { number, status: "ok" }`.
|
Output on success: `closed: { number, status: "ok" }`.
|
||||||
|
|
||||||
@@ -292,9 +298,10 @@ Truncation limit: 4000 chars.
|
|||||||
Output: `pr_diff: { number, diff[, truncated, original_length] }`.
|
Output: `pr_diff: { number, diff[, truncated, original_length] }`.
|
||||||
|
|
||||||
**`pr checkout <n>`**
|
**`pr checkout <n>`**
|
||||||
Fetches the PR head branch name from `GET /pulls/{index}` (`head.ref` field), then runs in the current working directory:
|
Fetches the PR head branch name from `GET /pulls/{index}` (`head.ref` field), then runs in the current working directory, three-cased on the local branch state so a re-checkout is idempotent (git refuses to fetch into the checked-out branch, and a moved PR head makes the plain fetch non-fast-forward):
|
||||||
1. `git fetch origin pull/<n>/head:<branch>`
|
1. Branch absent: `git fetch origin pull/<n>/head:<branch>`, then `git checkout <branch>`.
|
||||||
2. `git checkout <branch>`
|
2. Branch exists, not checked out: `git fetch origin +pull/<n>/head:<branch>` (force — the branch mirrors the PR head), then `git checkout <branch>`.
|
||||||
|
3. Branch currently checked out: `git fetch origin pull/<n>/head`, then `git merge --ff-only FETCH_HEAD`; if not fast-forwardable (local commits diverge from the PR head), `GIT_ERROR` with a help line explaining the divergence — local commits are never discarded silently.
|
||||||
Fetching `refs/pull/{index}/head` from the base repo works uniformly for same-repo and fork PRs — the head branch itself may live in a fork that is not a configured remote (see ADR 0011).
|
Fetching `refs/pull/{index}/head` from the base repo works uniformly for same-repo and fork PRs — the head branch itself may live in a fork that is not a configured remote (see ADR 0011).
|
||||||
Git subprocess failures (dirty worktree, network) map to `GIT_ERROR`, carrying git's first stderr line and a remediation help line.
|
Git subprocess failures (dirty worktree, network) map to `GIT_ERROR`, carrying git's first stderr line and a remediation help line.
|
||||||
Output: `checkout: { number, branch, status: "ok" }`.
|
Output: `checkout: { number, branch, status: "ok" }`.
|
||||||
@@ -347,10 +354,23 @@ Output: `edit: ok`, `label: <new-name-or-original-name>`.
|
|||||||
Not idempotent: a nonexistent label errors with `VALIDATION_ERROR` rather than reporting success (see ADR 0010).
|
Not idempotent: a nonexistent label errors with `VALIDATION_ERROR` rather than reporting success (see ADR 0010).
|
||||||
Output: `delete: ok`, `label: <name>`.
|
Output: `delete: ok`, `label: <name>`.
|
||||||
|
|
||||||
|
#### Search Commands
|
||||||
|
|
||||||
|
**`search issues <query> [flags]`** / **`search prs <query> [flags]`**
|
||||||
|
Full-text search within the current repository, added because `--search` on the list commands is forbidden and agents need a text-query escape hatch (the forbidden-flag error redirects here, mirroring gh-axi).
|
||||||
|
The positional `<query>` is required (`VALIDATION_ERROR` if missing).
|
||||||
|
Endpoint: `GET /repos/issues/search` with `q=<query>`, `type=issues` or `type=pulls`, and `owner=<owner>`.
|
||||||
|
The endpoint has no repo-name filter, so results are additionally filtered client-side to the current repository via each result's `repository` field — the standard client-side filtering policy applies, including its `count: N of T total` rule with `T` from the filtered set.
|
||||||
|
Flags: `--state <open|closed|all>` (default open); `--label <name>` (API-supported — comma-separated names); `--limit <n>` (default 30); `--fields <a,b,c>`.
|
||||||
|
Default output fields (both commands): `number`, `title`, `state`, `author`, `created` — a locator schema; search results are Issue-shaped for both types, and `draft`/`review` parity with `pr list` would require two extra fetches per result for a command whose job is finding the number to feed into `issue view` / `pr view`.
|
||||||
|
Output blocks: `issues:` / `pull_requests:`, matching the list commands.
|
||||||
|
Empty state: standard `<noun>[0]: (none)`.
|
||||||
|
|
||||||
#### Setup Command
|
#### Setup Command
|
||||||
|
|
||||||
**`setup`**
|
**`setup`**
|
||||||
Installs the bundled Agent Skill markdown into `~/.claude/skills/` (see ADR 0009).
|
Installs the bundled Agent Skill markdown into `~/.claude/skills/` (see ADR 0009).
|
||||||
|
The skill is a minimal pointer, not a command reference: its frontmatter description triggers on Gitea issue/PR/label work; its body says when to use gitea-axi (over tea, raw API calls, or git), lists the command groups with one-line summaries, and points at the bare `gitea-axi` dashboard and per-command `--help` for discovery — the CLI remains the single source of interface truth.
|
||||||
This is gitea-axi's primary fulfillment of AXI Principle 7 (Ambient context): an explicit setup command, matching gh-axi's `setup`.
|
This is gitea-axi's primary fulfillment of AXI Principle 7 (Ambient context): an explicit setup command, matching gh-axi's `setup`.
|
||||||
Idempotent: re-running reports already-installed/updated rather than failing.
|
Idempotent: re-running reports already-installed/updated rather than failing.
|
||||||
Output: `setup: { skill, path, status: <installed|updated|unchanged> }`.
|
Output: `setup: { skill, path, status: <installed|updated|unchanged> }`.
|
||||||
@@ -414,7 +434,10 @@ Exception: `issue comment` stays permissive — PRs genuinely share the comment
|
|||||||
|
|
||||||
Gitea has no aggregated `reviewDecision` field on the PR object.
|
Gitea has no aggregated `reviewDecision` field on the PR object.
|
||||||
gitea-axi computes it client-side from the reviews list (see ADR 0006).
|
gitea-axi computes it client-side from the reviews list (see ADR 0006).
|
||||||
Logic: `APPROVED` if at least one review has `official=true`, `stale=false`, `dismissed=false` and no non-dismissed `REQUEST_CHANGES` exists; `CHANGES_REQUESTED` if any non-dismissed `REQUEST_CHANGES` exists; `REVIEW_REQUIRED` otherwise.
|
Scope (official-first fallback): if any review on the PR carries `official=true`, only official reviews are considered (branch-protection semantics preserved); otherwise all reviews are considered — unprotected repos never produce official reviews, so without the fallback `APPROVED` would be unreachable there.
|
||||||
|
Logic within the considered set: `CHANGES_REQUESTED` if any non-dismissed `REQUEST_CHANGES` exists; `APPROVED` if at least one review with state `APPROVED` has `stale=false` and `dismissed=false`; `REVIEW_REQUIRED` otherwise.
|
||||||
|
The otherwise-bucket includes zero-review PRs and comment-only reviews: it renders as `required`, meaning "no conclusive review yet".
|
||||||
|
There is no `none` value — a deliberate three-value departure from gh-axi's four-value mapping, since Gitea offers no non-admin way to detect whether branch protection formally requires review.
|
||||||
On `pr list`, reviews for each PR are fetched in parallel (one extra HTTP call per PR in the list).
|
On `pr list`, reviews for each PR are fetched in parallel (one extra HTTP call per PR in the list).
|
||||||
|
|
||||||
### Context Override Flags
|
### Context Override Flags
|
||||||
@@ -476,12 +499,17 @@ API error responses are classified by HTTP status code and calling context:
|
|||||||
| 404 | called on `/repos/.../issues/{index}` | `ISSUE_NOT_FOUND` |
|
| 404 | called on `/repos/.../issues/{index}` | `ISSUE_NOT_FOUND` |
|
||||||
| 404 | called on `/repos/.../pulls/{index}` | `PR_NOT_FOUND` |
|
| 404 | called on `/repos/.../pulls/{index}` | `PR_NOT_FOUND` |
|
||||||
| 404 | other paths | `UNKNOWN` |
|
| 404 | other paths | `UNKNOWN` |
|
||||||
|
| 405 | any | `VALIDATION_ERROR` (body message surfaced — e.g. PR not mergeable due to conflicts or unmet checks; help: `pr update-branch <n>` or `pr checkout <n>` to resolve) |
|
||||||
|
| 409 | any | `VALIDATION_ERROR` (body message surfaced — e.g. head changed since merge was requested, or auto-merge already scheduled) |
|
||||||
| 422 | any | `VALIDATION_ERROR` (body message surfaced) |
|
| 422 | any | `VALIDATION_ERROR` (body message surfaced) |
|
||||||
| 429 | any | `RATE_LIMITED` (help: wait and retry, or reduce `--limit`) |
|
| 429 | any | `RATE_LIMITED` (help: wait and retry, or reduce `--limit`) |
|
||||||
| other | any | `UNKNOWN` |
|
| other | any | `UNKNOWN` |
|
||||||
|
|
||||||
`TEA_NOT_INSTALLED` is emitted if the tea binary is not found during credential discovery.
|
`TEA_NOT_INSTALLED` is emitted if the tea binary is not found during credential discovery.
|
||||||
`AUTH_REQUIRED` is also emitted when tea is installed but no login matches the detected hostname (help: `` Run `tea login add --url <host>` ``, or pass `--login <name>`).
|
Login matching against the detected hostname is a three-way split:
|
||||||
|
tea installed with zero logins configured → `AUTH_REQUIRED` (the tool was never set up; help: `` Run `tea login add` ``);
|
||||||
|
tea has logins but none match the detected hostname → `REPO_NOT_FOUND` (the repo is not recognized as belonging to a known Gitea instance — a remote URL's shape cannot reveal Gitea-ness, so an unmatched host most likely means a non-Gitea repo such as a GitHub clone; help: `` Run `tea login add --url <host>` `` if this is a Gitea instance, or pass `-R` + `--login`);
|
||||||
|
HTTP 401 from the API → `AUTH_REQUIRED` (token invalid or revoked), per the status table.
|
||||||
A `--login` value naming a nonexistent profile is `VALIDATION_ERROR`, listing the available profile names.
|
A `--login` value naming a nonexistent profile is `VALIDATION_ERROR`, listing the available profile names.
|
||||||
`GIT_ERROR` classifies non-zero git subprocess exits (currently only `pr checkout`), carrying git's first stderr line.
|
`GIT_ERROR` classifies non-zero git subprocess exits (currently only `pr checkout`), carrying git's first stderr line.
|
||||||
Error output is TOON-encoded to stdout (not stderr): `error: <message>`, `code: <CODE>`, and optionally `help[N]:` with suggestion lines.
|
Error output is TOON-encoded to stdout (not stderr): `error: <message>`, `code: <CODE>`, and optionally `help[N]:` with suggestion lines.
|
||||||
@@ -554,6 +582,8 @@ This means tests exercise the full reshaping pipeline — JSON parse, field extr
|
|||||||
- Local / unit tier: the fixture server runs fast with no external dependencies.
|
- Local / unit tier: the fixture server runs fast with no external dependencies.
|
||||||
Used for all command-level assertions.
|
Used for all command-level assertions.
|
||||||
- CI integration tier: a live disposable Gitea instance serves real API responses end-to-end, verifying that fixture recordings remain accurate and that the full HTTP pipeline works correctly.
|
- CI integration tier: a live disposable Gitea instance serves real API responses end-to-end, verifying that fixture recordings remain accurate and that the full HTTP pipeline works correctly.
|
||||||
|
CI runs on Gitea Actions on `git.alexion.dev` (where the PRs live), with the disposable Gitea as a docker service container pinned to the latest stable image tag, bumped deliberately.
|
||||||
|
The workflow file stays GitHub-Actions-compatible so the GitHub mirror can adopt it nearly verbatim later.
|
||||||
|
|
||||||
**Test runner:** Vitest.
|
**Test runner:** Vitest.
|
||||||
|
|
||||||
|
|||||||
21
.claude/tasks/0002-issue-list-filters-and-fields.md
Normal file
21
.claude/tasks/0002-issue-list-filters-and-fields.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0001-scaffold-and-issue-list-core
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
Complete the `issue list` flag surface on top of the minimal version from the tracer slice.
|
||||||
|
API-supported filters: `--label` (comma-separated names, passed through), `--assignee` (maps to `assigned_by`), `--author` (maps to `created_by`), `--milestone` (maps to `milestones`).
|
||||||
|
Client-side sort: `--sort <created|updated|comments>`, always descending, paginating fully before sorting while keeping the count line's `T` from the `X-Total-Count` header (sort reorders without changing membership).
|
||||||
|
Field selection: `--fields <a,b,c>` exposing the extra fields `body` (raw), `closedAt`, `labels` (joined names), `milestone` (title), `updatedAt`, `url`, built on the FieldDef extractor system.
|
||||||
|
`--search` is explicitly forbidden with a `VALIDATION_ERROR` redirecting to `search issues`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `--label`, `--assignee`, `--author`, and `--milestone` map to their Gitea API query params and filter server-side
|
||||||
|
- [ ] `--sort <created|updated|comments>` reorders descending client-side after full pagination; the count line still reports `T` from `X-Total-Count`
|
||||||
|
- [ ] `--fields` selects among the documented extra fields, each rendered via its FieldDef extractor (relative times, joined label names, milestone title)
|
||||||
|
- [ ] Output contains no `type` field
|
||||||
|
- [ ] `--search` fails with `VALIDATION_ERROR` (exit 2) and a help line pointing at `gitea-axi search issues "<query>"`
|
||||||
|
- [ ] Fixture-server tests cover each filter, client-side sort with pagination, `--fields` extraction, and the forbidden `--search`
|
||||||
26
.claude/tasks/0003-issue-view-and-truncation.md
Normal file
26
.claude/tasks/0003-issue-view-and-truncation.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0001-scaffold-and-issue-list-core
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
`issue view <n>` as a detail command, introducing the content-truncation and cleanBody machinery that later slices reuse.
|
||||||
|
Default output: `number`, `title`, `state`, `author`, `created`, `body` (truncated at 500 chars), plus `comment_count`.
|
||||||
|
`--comments` renders all comments with no count cap, each body truncated at 800 chars with cleanBody applied.
|
||||||
|
`--full` suppresses all truncation in the output — issue body and comment bodies alike.
|
||||||
|
cleanBody runs only when the raw body exceeds the truncation limit: it normalizes Gitea issue/PR URLs on the detected hostname to `Issue#N`/`PR#N`, strips markdown image embeds, removes long URLs, and collapses email-style quoted blocks; if cleaning brings the body under the limit the cleaned body is returned with a note, otherwise it is truncated with the inline hint.
|
||||||
|
Type guard: viewing a PR number fails with `VALIDATION_ERROR` ("issue #N is a pull request") and a `pr view <n>` help line.
|
||||||
|
No `type` field and no sub-issue augmentation.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `issue view <n>` renders the default detail fields plus `comment_count` via renderDetail
|
||||||
|
- [ ] Bodies over 500 chars are cleaned then truncated with the inline hint `"... (truncated, N chars total - use --full to see complete body)"`; bodies at or under the limit pass through untouched
|
||||||
|
- [ ] cleanBody normalizes issue/PR URLs using the detected hostname, strips image embeds and long URLs, and collapses quoted blocks
|
||||||
|
- [ ] `--comments` renders every comment (no cap), each body cleaned and truncated at 800 chars
|
||||||
|
- [ ] `--full` returns raw, untruncated body and comment bodies
|
||||||
|
- [ ] A PR number yields `VALIDATION_ERROR` (exit 2) with the "is a pull request" message and a `pr view <n>` help line, detected via the fetched object's `pull_request` field
|
||||||
|
- [ ] A nonexistent issue yields `ISSUE_NOT_FOUND` (exit 1)
|
||||||
|
- [ ] Single-entity next-step suggestions fill the actual issue number rather than a placeholder
|
||||||
|
- [ ] Fixture-server tests cover truncation boundaries, cleanBody transforms, `--comments`, `--full`, and the type guard
|
||||||
23
.claude/tasks/0004-issue-create-and-comment.md
Normal file
23
.claude/tasks/0004-issue-create-and-comment.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0003-issue-view-and-truncation
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The first mutations: `issue create` and `issue comment`, introducing the shared machinery for `--body-file`, name→ID resolution, and mutation output blocks.
|
||||||
|
`issue create` takes `--title` (required), `--body`/`--body-file`, `--assignee`, repeatable `--label` (resolved to label ID via the case-insensitive label lookup), and `--milestone` (resolved via the milestone name query); `--project` and `--type` are excluded.
|
||||||
|
Create output is the entity block `issue: { number, title, state, url }` with extra fields (`labels`, `assignees`, `milestone`, `body`) available via `--fields`.
|
||||||
|
`issue comment <n>` requires `--body`/`--body-file` and returns the created comment directly from the POST response as `comment: { number, author, created, body }` with the body truncated at 800 chars — no follow-up view call needed; the comment's own id is not output.
|
||||||
|
`issue comment` stays permissive toward PR numbers (PRs genuinely share the comment endpoint).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `issue create --title` creates an issue and outputs `issue: { number, title, state, url }` where `url` is `html_url`
|
||||||
|
- [ ] Missing `--title` fails immediately with `VALIDATION_ERROR` (exit 2) before any API call
|
||||||
|
- [ ] `--body-file <path>` reads the body from a file; `--body` and `--body-file` together are rejected
|
||||||
|
- [ ] `--label` resolves each name to an ID via case-insensitive lookup against the repo's labels; an unknown name yields `VALIDATION_ERROR`
|
||||||
|
- [ ] `--milestone` resolves the name via the milestone query; an unknown name yields `VALIDATION_ERROR`
|
||||||
|
- [ ] `issue comment <n> --body` posts and outputs `comment: { number, author, created, body }` built from the POST response, body cleaned and truncated at 800 chars, where `number` is the issue number
|
||||||
|
- [ ] `issue comment` accepts a PR number without a type-guard error
|
||||||
|
- [ ] Fixture-server tests cover create with labels/milestone, both body sources, comment output shape, and each validation failure
|
||||||
24
.claude/tasks/0005-issue-edit-close-reopen.md
Normal file
24
.claude/tasks/0005-issue-edit-close-reopen.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 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
|
||||||
19
.claude/tasks/0006-issue-delete-pin-unpin.md
Normal file
19
.claude/tasks/0006-issue-delete-pin-unpin.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0004-issue-create-and-comment
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The remaining simple issue mutations: `issue delete`, `issue pin`, `issue unpin`.
|
||||||
|
`issue delete <n>` hard-deletes via the DELETE endpoint (requires admin or owner permissions) and is deliberately not idempotent: a nonexistent issue errors with `ISSUE_NOT_FOUND` rather than reporting success (see ADR 0010).
|
||||||
|
`issue pin <n>` and `issue unpin <n>` call the pin endpoints and are idempotent, returning early with `Already pinned`/`Already unpinned` messages.
|
||||||
|
`issue lock`/`unlock`, `issue transfer`, and `issue subissue` remain excluded per the spec.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `issue delete <n>` outputs `issue: { number, status: "deleted" }` on success
|
||||||
|
- [ ] Deleting a nonexistent issue yields `ISSUE_NOT_FOUND` (exit 1), not idempotent success
|
||||||
|
- [ ] `issue pin <n>` outputs `issue: { number, state, pinned }`; pinning an already-pinned issue returns early with `message: "Already pinned"` and exit 0
|
||||||
|
- [ ] `issue unpin <n>` mirrors pin with `message: "Already unpinned"` on the no-op
|
||||||
|
- [ ] Fixture-server tests cover delete success, delete-missing refusal, and both pin/unpin no-ops
|
||||||
20
.claude/tasks/0007-issue-blocks-blocked-by.md
Normal file
20
.claude/tasks/0007-issue-blocks-blocked-by.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0001-scaffold-and-issue-list-core
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The two Gitea-specific dependency subcommand groups — `issue blocks <list|add|remove>` and `issue blocked-by <list|add|remove>` — over Gitea's blocks and dependencies endpoints.
|
||||||
|
`blocks` manages downstream dependents (issues that cannot proceed until this one is resolved); `blocked-by` manages upstream blockers.
|
||||||
|
List output blocks are `blocked_issues` and `blocking_issues`; add outputs `blocks: { issue, blocks }` / `blocked_by: { issue, blocked_by }`.
|
||||||
|
Idempotency: `add` of an existing relationship does a fetch-first check and returns `already: true`; `remove` of a nonexistent relationship is silent success; genuine validation failures (self-reference, cycles) surface as `VALIDATION_ERROR` via the 422 mapping.
|
||||||
|
No gh-axi equivalent exists — the interface shape follows this spec alone.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `issue blocks list <n>` and `issue blocked-by list <n>` render their respective output blocks with count lines and explicit empty states
|
||||||
|
- [ ] `issue blocks add <n> <target>` outputs `blocks: { issue: n, blocks: target }`; `issue blocked-by add <n> <blocker>` outputs `blocked_by: { issue: n, blocked_by: blocker }`
|
||||||
|
- [ ] Adding an existing relationship returns `already: true` (fetch-first check, no duplicate POST); removing a nonexistent relationship exits 0 silently-successfully
|
||||||
|
- [ ] Self-reference and cycle rejections from Gitea surface as `VALIDATION_ERROR` (exit 2) with the server's message
|
||||||
|
- [ ] Fixture-server tests cover list, add, idempotent re-add, remove, idempotent re-remove, and a 422 cycle rejection for both groups
|
||||||
25
.claude/tasks/0008-pr-list.md
Normal file
25
.claude/tasks/0008-pr-list.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 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
|
||||||
23
.claude/tasks/0009-pr-view-and-checks.md
Normal file
23
.claude/tasks/0009-pr-view-and-checks.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: [0003-issue-view-and-truncation, 0008-pr-list]
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
`pr view <n>` and `pr checks <n>`, built on the truncation machinery and the reviewDecision computation from earlier slices.
|
||||||
|
`pr view` always makes three API calls — the PR fetch and the reviews fetch in parallel, then the combined commit status once the head SHA is known — so default output includes `number`, `title`, `state`, `author`, `draft`, `merged`, `checks`, `body` (truncated at 500), `comment_count`, and `review_count` without extra flags.
|
||||||
|
The `checks` field renders as the summary string (`N passed, N failed[, N skipped][, N pending], N total`) or the explicit no-CI message; commit-status states map `success`→`pass`, `failure`/`error`/`warning`→`fail`, `skipped`→`skip`, `pending`→`pending`.
|
||||||
|
`--reviews` additionally fetches per-review inline comments and exposes Gitea-specific `official` and `stale` fields; `--comments` renders all comments at the 800-char truncation; `--full` suppresses all truncation.
|
||||||
|
`pr checks <n>` outputs the same summary line followed by a `checks` list of `{ name, conclusion }`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `pr view <n>` renders the default fields including `checks`, `comment_count`, and `review_count` from the three-call fetch pattern
|
||||||
|
- [ ] Commit-status states map to the four conclusions per the spec, `warning` counting as failure
|
||||||
|
- [ ] A PR with no statuses renders the `"0 passed, 0 failed — this PR has no CI checks configured"` message in both commands
|
||||||
|
- [ ] `--reviews` lists reviews with `official` and `stale` fields plus their inline comments
|
||||||
|
- [ ] `--comments` and `--full` behave as on `issue view` (800-char comment truncation with cleanBody; `--full` suppresses everything)
|
||||||
|
- [ ] `pr checks <n>` outputs the summary line followed by `{ name, conclusion }` rows
|
||||||
|
- [ ] A nonexistent PR yields `PR_NOT_FOUND` (exit 1)
|
||||||
|
- [ ] Fixture-server tests cover the status mapping including `skipped` and `warning`, the no-CI case, `--reviews`, and truncation behavior
|
||||||
23
.claude/tasks/0010-pr-create-and-comment.md
Normal file
23
.claude/tasks/0010-pr-create-and-comment.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0004-issue-create-and-comment
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
`pr create` and `pr comment`.
|
||||||
|
`pr create` takes `--title` (required), `--body`/`--body-file`, `--base`, `--head`, `--assignee`, `--reviewer`, repeatable `--label` (name-resolved), and `--milestone` (name-resolved); `--draft` and `--project` are excluded (no Gitea API support).
|
||||||
|
When `--head` is omitted it defaults to the current local branch from git; when `--base` is omitted it defaults to the repository's default branch fetched from the repo endpoint.
|
||||||
|
Idempotent: before creating, check for an existing open PR for the same base/head pair; if found, return `pull_request: { number, url, already: true }` instead of creating a duplicate.
|
||||||
|
Success output is the action block `created: { number, url }` — action block when the mutation ran, entity block when it was a no-op.
|
||||||
|
`pr comment <n>` posts through the shared issue-comment endpoint and returns the created comment as `comment: { number, author, created, body }` (800-char truncation), diverging from gh-axi's status-only block to save a follow-up view call (see ADR 0008).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `pr create --title` creates a PR and outputs `created: { number, url }`
|
||||||
|
- [ ] Omitted `--head` resolves to the current local branch; omitted `--base` resolves to the repo's default branch
|
||||||
|
- [ ] An existing open PR for the same branch pair short-circuits to `pull_request: { number, url, already: true }` with no duplicate created
|
||||||
|
- [ ] `--label` and `--milestone` resolve names case-insensitively with `VALIDATION_ERROR` on unknown names; `--assignee` and `--reviewer` pass through
|
||||||
|
- [ ] `pr comment <n> --body` outputs `comment: { number, author, created, body }` from the POST response, body truncated at 800 chars
|
||||||
|
- [ ] Missing required inputs (`--title` on create, body on comment) fail with `VALIDATION_ERROR` (exit 2) before any API call
|
||||||
|
- [ ] Fixture-server tests cover creation with defaults, the idempotent short-circuit, name resolution failures, and the comment output shape
|
||||||
22
.claude/tasks/0011-pr-edit-close-reopen.md
Normal file
22
.claude/tasks/0011-pr-edit-close-reopen.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0005-issue-edit-close-reopen
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The PR-side state mutations: `pr edit`, `pr close`, `pr reopen`, mirroring the issue-side slice with two PR-specific differences.
|
||||||
|
`pr edit <n>` supports `--title`, `--body`/`--body-file`, `--add-label`/`--remove-label` (same additive-endpoint and lookup rules as issues), `--add-assignee`/`--remove-assignee` (fetch-then-patch), `--add-reviewer`/`--remove-reviewer`, `--milestone` (name-resolved), and `--base`.
|
||||||
|
Reviewer mutations cannot use fetch-then-patch — `EditPullRequestOption` has no reviewers field — so they go through Gitea's dedicated requested-reviewers POST/DELETE endpoints (see ADR 0007 amendment).
|
||||||
|
`pr close <n>` supports `--comment` with the same two-call partial-failure policy as `issue close`, and returns `pull_request: { number, state, already: true }` when already closed or merged.
|
||||||
|
`pr reopen <n>` returns `pull_request: { number, state: "open", already: true }` when already open.
|
||||||
|
Success outputs follow the action-block pattern: `edited:`/`closed:`/`reopened:` with `{ number, status: "ok" }`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `pr edit` applies title, body, milestone, and base changes and outputs `edited: { number, status: "ok" }`
|
||||||
|
- [ ] Label and assignee mutations follow the same rules as `issue edit` (additive endpoints, fetch-then-patch, unapplied-label silent success)
|
||||||
|
- [ ] `--add-reviewer`/`--remove-reviewer` call the requested-reviewers endpoints with `{ reviewers: [login] }`
|
||||||
|
- [ ] `pr close --comment` posts the comment after the PATCH and surfaces a comment failure; closing an already-closed-or-merged PR returns the entity block with `already: true`
|
||||||
|
- [ ] `pr reopen` on an open PR returns the entity block with `already: true`; otherwise outputs `reopened: { number, status: "ok" }`
|
||||||
|
- [ ] Fixture-server tests cover reviewer add/remove, the merged-PR close no-op, and the reopen paths
|
||||||
23
.claude/tasks/0012-pr-merge-and-update-branch.md
Normal file
23
.claude/tasks/0012-pr-merge-and-update-branch.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0001-scaffold-and-issue-list-core
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
`pr merge` and `pr update-branch`.
|
||||||
|
`pr merge <n>` supports `--method` with all six Gitea methods (`merge`, `squash`, `rebase`, `rebase-merge`, `fast-forward-only`, `manually-merged`), the three common-method shorthands (`--merge`, `--squash`, `--rebase`), `--auto`, `--delete-branch`, `--body`/`--body-file`, `--subject`, and `--merge-commit-id`.
|
||||||
|
`--merge-commit-id` is required with `--method manually-merged` and rejected with any other method — both violations are `VALIDATION_ERROR` before any API call, as are conflicting shorthands.
|
||||||
|
Idempotent: an already-merged PR returns `pull_request: { number, state: "merged", merged_by, merged_at }` without calling the merge API.
|
||||||
|
Merge-blocked conditions surface through the standard 405/409 → `VALIDATION_ERROR` mapping with the server's message and remediation help lines.
|
||||||
|
`pr update-branch <n>` merges the base branch into the PR head via the update endpoint with `--style <merge|rebase>` (default merge).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `--method` accepts all six methods and the shorthands map to their methods; conflicting or duplicate action flags yield `VALIDATION_ERROR` (exit 2) before any API call
|
||||||
|
- [ ] `--merge-commit-id` without `manually-merged`, or `manually-merged` without `--merge-commit-id`, both yield `VALIDATION_ERROR` locally
|
||||||
|
- [ ] Successful merge outputs `merged: { number, status: "ok", method }`
|
||||||
|
- [ ] An already-merged PR short-circuits to the entity block with `merged_by` and `merged_at`, exit 0, no merge API call
|
||||||
|
- [ ] A 405 not-mergeable response surfaces as `VALIDATION_ERROR` with help suggesting `pr update-branch <n>` or `pr checkout <n>`
|
||||||
|
- [ ] `pr update-branch <n> --style rebase` calls the update endpoint with the style param and outputs `updated: { number, status: "ok" }`
|
||||||
|
- [ ] Fixture-server tests cover each method, the local validations, the idempotent no-op, and the 405/409 mappings
|
||||||
19
.claude/tasks/0013-pr-review.md
Normal file
19
.claude/tasks/0013-pr-review.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0001-scaffold-and-issue-list-core
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
`pr review <n>` with the three action flags `--approve`, `--request-changes`, `--comment`, plus `--body`/`--body-file`.
|
||||||
|
Exactly one action flag is required: zero or multiple yield `VALIDATION_ERROR` before any API call, mirroring the merge shorthand-conflict rule.
|
||||||
|
Body requirements are not pre-validated locally — if Gitea rejects a body-less review event, its 422 surfaces as `VALIDATION_ERROR` with the server's message.
|
||||||
|
Output: `review: { number, action }`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Each action flag submits the corresponding review event and outputs `review: { number, action }`
|
||||||
|
- [ ] Zero action flags, or more than one, yield `VALIDATION_ERROR` (exit 2) with no API call
|
||||||
|
- [ ] A server-side 422 for a missing body surfaces as `VALIDATION_ERROR` carrying Gitea's message
|
||||||
|
- [ ] `--body-file` works as everywhere else
|
||||||
|
- [ ] Fixture-server tests cover all three actions, the flag-count validations, and the 422 passthrough
|
||||||
23
.claude/tasks/0014-pr-diff-and-checkout.md
Normal file
23
.claude/tasks/0014-pr-diff-and-checkout.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0003-issue-view-and-truncation
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The two PR commands that touch content and the local worktree: `pr diff` and `pr checkout`.
|
||||||
|
`pr diff <n>` fetches the raw diff from the `.diff` endpoint and truncates at 4000 chars, signaling truncation with separate `truncated: true` and `original_length: N` fields (not an inline hint) plus a prepended `--full` suggestion; `--full` suppresses diff truncation.
|
||||||
|
Output: `pr_diff: { number, diff[, truncated, original_length] }`.
|
||||||
|
`pr checkout <n>` reads the PR head branch name from the PR fetch, then fetches `refs/pull/{n}/head` from origin — which works uniformly for same-repo and fork PRs (see ADR 0011) — three-cased on local branch state so re-checkout is idempotent:
|
||||||
|
absent branch → fetch into it and check out; existing unchecked-out branch → force-fetch (the branch mirrors the PR head) and check out; currently checked-out branch → plain fetch then `--ff-only` merge, failing with `GIT_ERROR` and a divergence explanation if local commits diverge — never discarding them silently.
|
||||||
|
This slice introduces `GIT_ERROR`: non-zero git subprocess exits map to it, carrying git's first stderr line and a remediation help line.
|
||||||
|
Output: `checkout: { number, branch, status: "ok" }`.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `pr diff <n>` outputs the diff, adding `truncated: true` and `original_length` when over 4000 chars plus a `--full` next-step suggestion; `--full` returns the raw diff
|
||||||
|
- [ ] `pr checkout <n>` handles all three local-branch cases and re-running it is idempotent
|
||||||
|
- [ ] A checked-out branch that has diverged from the PR head fails with `GIT_ERROR` and an explanatory help line, leaving local commits intact
|
||||||
|
- [ ] Other git failures (dirty worktree, network) map to `GIT_ERROR` with git's first stderr line
|
||||||
|
- [ ] Checkout works for a fork PR whose head repo is not a configured remote (via `refs/pull/{n}/head`)
|
||||||
|
- [ ] Tests cover diff truncation boundaries and the three checkout cases (git behavior exercised against a scratch repository, API responses from the fixture server)
|
||||||
22
.claude/tasks/0015-label-commands.md
Normal file
22
.claude/tasks/0015-label-commands.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0004-issue-create-and-comment
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The label command group: `label list`, `label create`, `label edit`, `label delete`.
|
||||||
|
`label list` takes `--limit` (default 500) and outputs a count line plus `labels: [ { name } ]`.
|
||||||
|
`label create` requires `--name` and `--color` (hex without `#`; the `#` is prepended before the API call) with optional `--description`; it is idempotent via a case-insensitive existence check, reporting `create: already_exists` instead of failing.
|
||||||
|
`label edit <name>` and `label delete <name>` resolve the positional name via the standard case-insensitive label lookup with `VALIDATION_ERROR` when not found.
|
||||||
|
`label delete` is deliberately not idempotent — a nonexistent label is refused rather than reported as success (see ADR 0010).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `label list` renders the count line and `labels:` block; empty repos get the explicit empty state
|
||||||
|
- [ ] `label create --name --color` creates the label, prepending `#` to the color, and outputs `created: ok` + `label: <name>`
|
||||||
|
- [ ] Creating an existing label (case-insensitive) outputs `create: already_exists` + the existing name, exit 0
|
||||||
|
- [ ] `label edit <name>` applies `--name`/`--color`/`--description` and outputs `edit: ok` + the resulting name
|
||||||
|
- [ ] `label edit`/`label delete` on an unknown name yield `VALIDATION_ERROR` (exit 2)
|
||||||
|
- [ ] `label delete <name>` outputs `delete: ok` + `label: <name>`
|
||||||
|
- [ ] Fixture-server tests cover create, idempotent re-create, edit, delete, and the unknown-name refusals
|
||||||
21
.claude/tasks/0016-search-commands.md
Normal file
21
.claude/tasks/0016-search-commands.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0008-pr-list
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The full-text escape hatch: `search issues <query>` and `search prs <query>`, the destination of the forbidden `--search` redirects.
|
||||||
|
Both hit Gitea's repo-issues search endpoint with the query, a `type` of issues or pulls, and the owner param; since the endpoint has no repo-name filter, results are filtered client-side to the current repository via each result's repository field, following the client-side filtering policy including its count-line rule.
|
||||||
|
The positional query is required (`VALIDATION_ERROR` if missing).
|
||||||
|
Flags: `--state` (default open), `--label` (comma-separated names, API-supported), `--limit` (default 30), `--fields`.
|
||||||
|
Both commands use the locator schema (`number`, `title`, `state`, `author`, `created`) — search finds the number, `issue view`/`pr view` load the detail — and output blocks `issues:`/`pull_requests:` matching the list commands.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] `search issues "<query>"` and `search prs "<query>"` query the search endpoint with the right `type` and owner, then filter to the current repo client-side
|
||||||
|
- [ ] The count line reports `count: N of T total` with `T` from the client-side-filtered set
|
||||||
|
- [ ] A missing query yields `VALIDATION_ERROR` (exit 2)
|
||||||
|
- [ ] `--state`, `--label`, `--limit`, and `--fields` work; default fields are the locator schema
|
||||||
|
- [ ] Empty results emit the standard `<noun>[0]: (none)` empty state
|
||||||
|
- [ ] Fixture-server tests cover both types, cross-repo results being filtered out, and the missing-query validation
|
||||||
22
.claude/tasks/0017-dashboard.md
Normal file
22
.claude/tasks/0017-dashboard.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0008-pr-list
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The two-tier dashboard: `gitea-axi` with no arguments, preceded by the `bin:` + `description:` header from axi-sdk-js and followed by next-step suggestions (see ADR 0012).
|
||||||
|
The short tier fetches up to 3 open issues (`number`, `title`, `state`, `author`) and up to 3 open PRs (`number`, `title`, `author`, `review`) in parallel with `limit=3`, computing `review` via the same parallel review fetch as `pr list`; its `help:` block always hints at `--full`.
|
||||||
|
The full tier (`--full`) shows open PRs as a TOON table (default fields `number`, `title`, `author`, `labels`, `review`; capped at 20 rows with a standard count line) and open issue counts grouped by label — aggregating all pages of open issues at page size 50 up to a hard cap of 1000, suffixing counts with `+` if capped; each issue contributes to all its labels, with a nonzero-only `unlabeled` row.
|
||||||
|
Output blocks in both tiers: a `repo:` line, then `prs:` and `issues:`; empty states are the raw strings `prs: 0 open` / `issues: 0 open`.
|
||||||
|
Issue fetching passes `type=issues`; outside a recognizable Gitea repo the dashboard errors with `REPO_NOT_FOUND` and `-R` + `--login` help — even when invoked by the SessionStart hook (see ADR 0009).
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] Bare `gitea-axi` renders the header, `repo:` line, up to 3 issues and 3 PRs with the specified fields (including the computed `review`), and a `help:` block hinting at `--full`
|
||||||
|
- [ ] `gitea-axi --full` renders the PR table capped at 20 rows with `count: 20 of T total` and issue counts grouped by label
|
||||||
|
- [ ] Label aggregation paginates to the 1000-issue cap, suffixes counts with `+` when capped, counts each issue under all its labels, and shows `unlabeled` only when nonzero
|
||||||
|
- [ ] Empty states render `prs: 0 open` / `issues: 0 open` as raw strings
|
||||||
|
- [ ] Issue fetches pass `type=issues` so PRs never appear in the issue block
|
||||||
|
- [ ] Outside a Gitea repo the dashboard exits with `REPO_NOT_FOUND` and help mentioning `-R` and `--login`
|
||||||
|
- [ ] Fixture-server tests cover both tiers, the cap-and-suffix behavior, empty states, and the no-repo error
|
||||||
21
.claude/tasks/0018-setup-skill-and-hooks.md
Normal file
21
.claude/tasks/0018-setup-skill-and-hooks.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0017-dashboard
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
Ambient-context distribution: the bundled Agent Skill, the `setup` command, the opt-in `setup hooks`, and the shadowed `update` command (see ADRs 0009 and 0013).
|
||||||
|
Author the Agent Skill markdown as a minimal pointer, not a command reference: frontmatter description triggering on Gitea issue/PR/label work; a body saying when to use gitea-axi over tea, raw API calls, or git; one-line command-group summaries; and pointers at the bare dashboard and per-command `--help` — the CLI stays the single source of interface truth.
|
||||||
|
`setup` installs the skill into the user-level skills directory, idempotently reporting installed/updated/unchanged.
|
||||||
|
`setup hooks` installs a SessionStart hook via axi-sdk-js's `installSessionStartHooks()` for Claude Code, Codex, and OpenCode; the hook runs the bare binary (short dashboard tier) at session start.
|
||||||
|
`update` shadows the SDK's built-in self-update command, failing with `VALIDATION_ERROR` and a help line pointing at the npm update command, keeping the ten-code error list intact.
|
||||||
|
There is no postinstall script — skill and hook installation are always explicit user actions.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] The Agent Skill markdown is bundled in the package and follows the minimal-pointer shape (trigger description, when-to-use, command-group one-liners, discovery pointers)
|
||||||
|
- [ ] `gitea-axi setup` installs the skill and outputs `setup: { skill, path, status }`; re-running reports `updated` or `unchanged` rather than failing
|
||||||
|
- [ ] `gitea-axi setup hooks` registers the SessionStart hook for all three integrations via the SDK and outputs the `hooks:` block with a restart help line; managed entries are updated in place on re-run
|
||||||
|
- [ ] `gitea-axi update` fails with `VALIDATION_ERROR` (exit 2) and the npm update help line; the SDK's `UPDATE_ERROR` never surfaces
|
||||||
|
- [ ] Tests cover the setup idempotency states and the update shadow (hook installation verified against a temp home directory)
|
||||||
26
.claude/tasks/0019-ci-integration-tier.md
Normal file
26
.claude/tasks/0019-ci-integration-tier.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0001-scaffold-and-issue-list-core
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The end-to-end tier of the test suite: a CI workflow that runs the local vitest suite plus end-to-end tests against a live disposable Gitea instance, verifying that fixture recordings remain accurate and the full HTTP pipeline works.
|
||||||
|
|
||||||
|
This project's test taxonomy has three tiers (recorded here because the committed spec's two-tier wording is frozen):
|
||||||
|
|
||||||
|
- **Unit tests** — functions within a single file, no I/O (e.g. `parseRemoteUrl`, `relativeTime`).
|
||||||
|
- **Integration tests** — functionality across files, driven at the CLI seam (argv in, stdout/exit-code out) against the fixture server; what the spec's "Two-tier test strategy" calls the "local / unit tier".
|
||||||
|
- **End-to-end tests** — the same CLI seam against a live disposable Gitea instance; what the spec calls the "CI integration tier". This task builds this tier.
|
||||||
|
|
||||||
|
CI runs on Gitea Actions on the operator's instance, with the disposable Gitea as a docker service container pinned to the latest stable image tag (bumped deliberately, not floating).
|
||||||
|
The end-to-end tests provision what they need on the disposable instance (repo, token, seed issues/PRs) and then exercise the real CLI seam against it.
|
||||||
|
The workflow file stays GitHub-Actions-compatible so the GitHub mirror can adopt it nearly verbatim later.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] A workflow runs on push/PR on Gitea Actions, executing the unit and integration tiers (the local vitest suite) and the end-to-end tier
|
||||||
|
- [ ] The disposable Gitea runs as a service container pinned to a specific stable image tag
|
||||||
|
- [ ] End-to-end tests provision their own repo, token, and seed data on the disposable instance, then assert real CLI output and exit codes for at least the tracer command set
|
||||||
|
- [ ] The workflow uses only syntax that works verbatim (or near-verbatim) on GitHub Actions
|
||||||
|
- [ ] A fixture-vs-live divergence in a covered response shape fails the end-to-end tier
|
||||||
18
.claude/tasks/0020-npm-distribution.md
Normal file
18
.claude/tasks/0020-npm-distribution.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
spec: gitea-axi
|
||||||
|
blocked-by: 0018-setup-skill-and-hooks
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
Publish readiness for the unscoped `gitea-axi` npm package.
|
||||||
|
Package metadata (name, description, repository, license, engines for Node 20+, ESM), the `gitea-axi` bin entry, and the bundled Agent Skill file included in the published artifact.
|
||||||
|
No postinstall script — the install delivers the CLI binary only, and skill installation stays behind the explicit `setup` command.
|
||||||
|
Verify the packed artifact: a global install from the packed tarball yields a working binary whose `setup` finds the bundled skill.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [ ] The packed tarball contains the built CLI, the bin entry, and the Agent Skill markdown, and nothing declares a postinstall script
|
||||||
|
- [ ] A global install from the tarball puts a working `gitea-axi` on the PATH (dashboard header, `--help`, and `setup` all function)
|
||||||
|
- [ ] Package metadata is complete: unscoped name, description, repository URL, license, Node 20+ engines, ESM module type
|
||||||
|
- [ ] The publish flow (registry target, access, prepack build) is documented or scripted so publishing is a single command
|
||||||
5
CLAUDE.md
Normal file
5
CLAUDE.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# gitea-axi — Agent Instructions
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
Any commit message you write must follow the Conventional Commits specification as documented in [CONVENTIONAL-COMMITS.md](CONVENTIONAL-COMMITS.md).
|
||||||
57
CONVENTIONAL-COMMITS.md
Normal file
57
CONVENTIONAL-COMMITS.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Conventional Commits
|
||||||
|
|
||||||
|
> Sourced from [conventionalcommits.org/en/v1.0.0](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
|
> by the Conventional Commits authors, licensed under
|
||||||
|
> [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The Conventional Commits specification is a lightweight convention on top of commit messages.
|
||||||
|
It provides an easy set of rules for creating an explicit commit history; which makes it easier to write automated tools on top of.
|
||||||
|
This convention dovetails with [SemVer](http://semver.org), by describing the features, fixes, and breaking changes made in commit messages.
|
||||||
|
|
||||||
|
The commit message should be structured as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>[optional scope]: <description>
|
||||||
|
|
||||||
|
[optional body]
|
||||||
|
|
||||||
|
[optional footer(s)]
|
||||||
|
```
|
||||||
|
|
||||||
|
The commit contains the following structural elements, to communicate intent to the consumers of your library:
|
||||||
|
|
||||||
|
1. **fix:** a commit of the _type_ `fix` patches a bug in your codebase (this correlates with `PATCH` in Semantic Versioning).
|
||||||
|
2. **feat:** a commit of the _type_ `feat` introduces a new feature to the codebase (this correlates with `MINOR` in Semantic Versioning).
|
||||||
|
3. **BREAKING CHANGE:** a commit that has a footer with a token `BREAKING CHANGE:`, or appends a `!` after the type/scope, introduces a breaking API change (correlating with `MAJOR` in Semantic Versioning). A BREAKING CHANGE can be part of commits of any _type_.
|
||||||
|
4. _types_ other than `fix:` and `feat:` are allowed, for example `build:`, `chore:`, `ci:`, `docs:`, `style:`, `refactor:`, `perf:`, `test:`, and others.
|
||||||
|
5. _footers_ other than `BREAKING CHANGE: <description>` may be provided and follow a convention similar to [git trailer format](https://git-scm.com/docs/git-interpret-trailers).
|
||||||
|
|
||||||
|
Additional types are not mandated by the Conventional Commits specification, and have no implicit effect in Semantic Versioning (unless they include a BREAKING CHANGE).
|
||||||
|
A scope may be provided to a commit's type, to provide additional contextual information and is contained within parenthesis, e.g., `feat(parser): add ability to parse arrays`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).
|
||||||
|
|
||||||
|
1. Commits MUST be prefixed with a type, which consists of a noun, `feat`, `fix`, etc., followed by the OPTIONAL scope, OPTIONAL `!`, and REQUIRED terminal colon and space.
|
||||||
|
2. The type `feat` MUST be used when a commit adds a new feature to your application or library.
|
||||||
|
3. The type `fix` MUST be used when a commit represents a bug fix for your application.
|
||||||
|
4. A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., `fix(parser):`.
|
||||||
|
5. A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., _fix: array parsing issue when multiple spaces were contained in string_.
|
||||||
|
6. A longer commit body MAY be provided after the short description, providing additional contextual information about the code changes. The body MUST begin one blank line after the description.
|
||||||
|
7. A commit body is free-form and MAY consist of any number of newline separated paragraphs.
|
||||||
|
8. One or more footers MAY be provided one blank line after the body. Each footer MUST consist of a word token, followed by either a `:<space>` or `<space>#` separator, followed by a string value (this is inspired by the [git trailer convention](https://git-scm.com/docs/git-interpret-trailers)).
|
||||||
|
9. A footer's token MUST use `-` in place of whitespace characters, e.g., `Acked-by` (this helps differentiate the footer section from a multi-paragraph body). An exception is made for `BREAKING CHANGE`, which MAY also be used as a token.
|
||||||
|
10. A footer's value MAY contain spaces and newlines, and parsing MUST terminate when the next valid footer token/separator pair is observed.
|
||||||
|
11. Breaking changes MUST be indicated in the type/scope prefix of a commit, or as an entry in the footer section.
|
||||||
|
12. If included as a footer, a breaking change MUST consist of the uppercase text `BREAKING CHANGE`, followed by a colon, space, and description, e.g., _BREAKING CHANGE: environment variables now take precedence over config files_.
|
||||||
|
13. If included in the type/scope prefix, breaking changes MUST be indicated by a `!` immediately before the `:`. If `!` is used, `BREAKING CHANGE:` MAY be omitted from the footer section, and the commit description SHALL be used to describe the breaking change.
|
||||||
|
14. Types other than `feat` and `fix` MAY be used in your commit messages, e.g., _docs: correct spelling of CHANGELOG_.
|
||||||
|
15. The units of information that make up Conventional Commits MUST NOT be treated as case sensitive by implementors, with the exception of BREAKING CHANGE which MUST be uppercase.
|
||||||
|
16. BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE, when used as a token in a footer.
|
||||||
Reference in New Issue
Block a user