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:
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
|
||||
Reference in New Issue
Block a user