From bbc73008a3641bda2691a6c814163e878f7385d6 Mon Sep 17 00:00:00 2001 From: Defame1297 Date: Sun, 30 Aug 2026 12:42:20 +0000 Subject: [PATCH] chore(gitea): regenerate the flat content mirror after the ADR-0020 retrofit Generated output, not authored content: scripts/sync-plugin-content.sh --all. Claude Code has no .apm/ awareness, so this compiled mirror must track .apm/ or the check-plugin-content-sync pre-push hook reports drift. Deferred to a single commit at the end of the wave on purpose. sync_dir runs rm -rf before every copy, so running it while seven agents were editing the same plugin would have raced them; agents were told not to sync for that reason. Refs #99 --- plugins/gitea/skills/gitea-branches/README.md | 12 +++ plugins/gitea/skills/gitea-branches/SKILL.md | 30 +++--- .../gitea-branches/references/branches.md | 2 +- .../gitea-branches/references/commits.md | 2 +- plugins/gitea/skills/gitea-files/README.md | 3 +- plugins/gitea/skills/gitea-files/SKILL.md | 50 +++++----- .../skills/gitea-files/references/examples.md | 65 ------------- .../skills/gitea-files/references/reading.md | 46 ++++++++++ .../skills/gitea-files/references/sources.md | 14 +-- .../skills/gitea-files/references/writing.md | 76 +++++++++++++++ plugins/gitea/skills/gitea-issues/README.md | 20 +++- plugins/gitea/skills/gitea-issues/SKILL.md | 92 ++++++------------- .../gitea-issues/references/enrichments.md | 3 +- .../skills/gitea-issues/references/sources.md | 4 +- .../skills/gitea-labels-milestones/README.md | 8 +- .../skills/gitea-labels-milestones/SKILL.md | 34 +++---- .../references/label-inference.md | 6 +- .../references/labels.md | 12 ++- plugins/gitea/skills/gitea-prs/README.md | 6 +- plugins/gitea/skills/gitea-prs/SKILL.md | 67 +++++--------- .../gitea-prs/references/pull-requests.md | 6 +- .../skills/gitea-prs/references/reviews.md | 27 ++++-- .../skills/gitea-prs/references/sources.md | 6 +- plugins/gitea/skills/gitea-releases/SKILL.md | 29 +++--- .../gitea-releases/references/sources.md | 3 +- plugins/gitea/skills/gitea-workflow/README.md | 7 +- plugins/gitea/skills/gitea-workflow/SKILL.md | 66 ++++--------- .../references/number-resolution.md | 19 ++++ .../gitea-workflow/references/skill-index.md | 22 +++++ .../gitea-workflow/references/sources.md | 8 +- .../references/status-checkin.md | 15 +++ 31 files changed, 407 insertions(+), 353 deletions(-) delete mode 100644 plugins/gitea/skills/gitea-files/references/examples.md create mode 100644 plugins/gitea/skills/gitea-files/references/reading.md create mode 100644 plugins/gitea/skills/gitea-files/references/writing.md create mode 100644 plugins/gitea/skills/gitea-workflow/references/number-resolution.md create mode 100644 plugins/gitea/skills/gitea-workflow/references/skill-index.md create mode 100644 plugins/gitea/skills/gitea-workflow/references/status-checkin.md diff --git a/plugins/gitea/skills/gitea-branches/README.md b/plugins/gitea/skills/gitea-branches/README.md index 1be8bcc..3a99f72 100644 --- a/plugins/gitea/skills/gitea-branches/README.md +++ b/plugins/gitea/skills/gitea-branches/README.md @@ -10,6 +10,18 @@ history (list commits, get a single commit's full detail) against a Gitea reposi pagination conventions specific to Gitea's API (e.g. refusing to delete a protected branch without explicit confirmation, and treating unexpected 404s as possible masked 403s). +## Boundaries + +This skill operates on the Gitea server via the MCP tools, never on your local checkout. Branch +and commit-history work against the working copy belongs to `git-branches` and `git-history`. +Branch references that only exist relative to a pull request — a PR's head or base branch, and +cross-repo fork PR heads in particular — belong to `gitea-prs`; `list_branches` cannot see a fork's +head at all. + +The skill triggers on phrasings like "list branches", "create a branch", "delete a branch", +"what commits are on this branch", "show commit ", and "what changed in that commit", even +when the user does not say "Gitea", as long as the repo's remote is a Gitea instance. + ## Before you start Requires a Gitea MCP server configured with a token that has `write:repository` scope. This is diff --git a/plugins/gitea/skills/gitea-branches/SKILL.md b/plugins/gitea/skills/gitea-branches/SKILL.md index 654ebfc..fc112ff 100644 --- a/plugins/gitea/skills/gitea-branches/SKILL.md +++ b/plugins/gitea/skills/gitea-branches/SKILL.md @@ -2,22 +2,16 @@ name: gitea-branches description: > - Use when managing Gitea repository branches — listing, creating, or deleting - branches — or inspecting commit history within a Gitea repo: listing commits - (optionally filtered by branch or file path) or getting full detail for a - single commit by SHA. Triggers on "list branches", "create a branch", - "delete a branch", "what commits are on this branch", "show commit ", - "what changed in that commit" — even if the user doesn't say "Gitea" - explicitly, as long as the repo's remote is a Gitea instance. Do not use for - local git branch/commit operations on your working copy (use git-branches or - git-history) or for PR-side branch references like cross-repo fork PR heads - (use gitea-prs). + Use when listing, creating, or deleting branches in a Gitea repository, or + reading its commit history — even when the user does not say "Gitea". Not a + local working copy's branches -> `git-branches`. Not local history -> + `git-history`. Not a PR's head or base branch -> `gitea-prs`. compatibility: Requires Gitea MCP server configured with a token with write:repository scope; this is confirmed to gate list_branches, create_branch, and delete_branch (Gitea gates reads behind write scope for repo-scoped operations), and is inferred by analogy (not explicitly confirmed by source docs) to also gate list_commits and get_commit. Requires git remote "origin" pointing to the Gitea instance. metadata: category: integration - version: "0.1.1" + version: "0.1.2" source_keys: - gitea-mcp-repo - gitea-mcp-slim-go @@ -28,11 +22,9 @@ allowed-tools: Bash mcp__gitea__list_branches mcp__gitea__create_branch mcp__git ## Gotchas -- **Never delete a protected branch (`main`/`master` by name, or `protected: true` from `list_branches`) without explicit confirmation.** `delete_branch` is a direct API call, not a local `git push` — there is no client-side force-push guard protecting it. Name-matching `main`/`master` is a convenient default but not authoritative — a repo can protect a differently-named default branch. When in doubt, call `list_branches` first and check `protected` on the target; treat deletion of any protected branch as a hard refusal unless the user explicitly confirms in the conversation. -- **404 may actually mean 403.** Gitea hides permission errors as not-found to avoid leaking resource existence. If any of these five tools returns 404 unexpectedly, check token scope (see `references/branches.md` / `references/commits.md`) before concluding the branch or commit doesn't exist. -- **Pagination is manual.** `list_branches` and `list_commits` return one page at a time — no auto-pagination in the MCP layer. When you need a complete list, iterate `page: 1, 2, ...` until the returned count is less than `per_page`. -- **Owner/repo always come from the git remote, never from `get_me`.** Resolve them via `git remote get-url origin` (Step 1 below). `get_me`/`list_my_repos` are blocked under the token scopes this skill assumes. -- **`create_branch`'s source is `old_branch`, not "wherever gitea-mcp feels like."** Omitting `old_branch` forks from the repo's server-side default branch — not necessarily the branch you're currently working on locally. If you want to branch from your current checkout, pass `old_branch` explicitly. +- **404 often means 403.** Gitea masks permission errors as not-found; on an unexpected one, check token scope before reporting a branch or commit missing. +- **Nothing auto-paginates.** `list_branches` and `list_commits` return one page; iterate `page` until the returned count is below `per_page`. +- **`delete_branch` has no force-push guard.** Check `protected` from `list_branches` first and require explicit confirmation — a protected branch need not be named `main`. ## Step 1 — Resolve owner and repo @@ -42,7 +34,7 @@ Before any tool call, extract `owner` and `repo` from the git remote: git remote get-url origin ``` -If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." +`get_me` and `list_my_repos` are blocked under the token scope this skill assumes, so the remote is the only source. If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." ## Step 2 — Dispatch @@ -54,7 +46,7 @@ If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea r | `/gitea-branches commits [on ] [touching ]` | List commit history | | `/gitea-branches commit ` | Get full detail for one commit | -For branch operations (list/create/delete), read `references/branches.md`. +For branch operations (list/create/delete), read `references/branches.md` — it carries the call signatures, the `old_branch` source rule, and the protected-branch refusal in full. For commit operations (list/get), read `references/commits.md`. ## Step 3 — Report @@ -63,4 +55,4 @@ For reads: display branches as name + protected flag; display commits as SHA (sh For writes (create/delete): confirm the action taken, the branch name, and (for create) the base it forked from. -For errors: surface the HTTP code and message. If a 404 is unexpected, re-check token scope per the Gotchas above before reporting "not found" to the user. +For errors: surface the HTTP code and message, applying the 404 gotcha above before reporting "not found" to the user. diff --git a/plugins/gitea/skills/gitea-branches/references/branches.md b/plugins/gitea/skills/gitea-branches/references/branches.md index b94754d..ff4e239 100644 --- a/plugins/gitea/skills/gitea-branches/references/branches.md +++ b/plugins/gitea/skills/gitea-branches/references/branches.md @@ -65,7 +65,7 @@ A branch name collision returns `409 Conflict`. delete_branch owner: repo: branch: ``` -Before calling this, see the hard-refusal Gotcha in SKILL.md. If the target branch's name isn't +Before calling this, see the `delete_branch` Gotcha in SKILL.md. If the target branch's name isn't obviously a scratch/feature branch, call `list_branches` first and check `protected` on the matching entry — name-matching `main`/`master` alone isn't authoritative, since a repo can protect a differently-named default branch. Confirm explicitly with the user before deleting anything diff --git a/plugins/gitea/skills/gitea-branches/references/commits.md b/plugins/gitea/skills/gitea-branches/references/commits.md index fddeff5..19f8b3c 100644 --- a/plugins/gitea/skills/gitea-branches/references/commits.md +++ b/plugins/gitea/skills/gitea-branches/references/commits.md @@ -42,7 +42,7 @@ Dispatch defaults: **Response:** one object per commit: `sha`, `html_url`, `created`, `message` (when available), `author` (`{name, email, date}`, when available). -Paginate per the manual-pagination Gotcha in SKILL.md if you need more than one page of history. +Paginate per the pagination Gotcha in SKILL.md if you need more than one page of history. ## `get_commit` diff --git a/plugins/gitea/skills/gitea-files/README.md b/plugins/gitea/skills/gitea-files/README.md index 52f560d..333e899 100644 --- a/plugins/gitea/skills/gitea-files/README.md +++ b/plugins/gitea/skills/gitea-files/README.md @@ -19,5 +19,6 @@ Describe the file task: read a file or directory, walk a tree, create/update a f | File | Purpose | |------|---------| | `SKILL.md` | Skill instructions for agents | -| `references/examples.md` | Canonical call sequences: branch + file + PR, recovering a missing SHA before an update, deleting a file | +| `references/reading.md` | Loaded for the read flow: the three read tools, `ref`/`tree_sha` selection, tree pagination, and why a listing is not a SHA source | +| `references/writing.md` | Loaded for the write flow: the SHA-first create/update/delete sequences, `new_branch_name`, the worked branch + file + PR sequence, and failed-write triage | | `references/sources.md` | Research sources backing the SHA/concurrency and direct-commit-vs-PR guidance | diff --git a/plugins/gitea/skills/gitea-files/SKILL.md b/plugins/gitea/skills/gitea-files/SKILL.md index f687f70..18e47cb 100644 --- a/plugins/gitea/skills/gitea-files/SKILL.md +++ b/plugins/gitea/skills/gitea-files/SKILL.md @@ -2,15 +2,9 @@ name: gitea-files description: > - Use when reading or writing individual files or directory trees in a Gitea repository via the - Gitea MCP server: reading a file's contents, listing a directory, walking a full repository - tree, creating a new file, updating an existing file, or deleting a file. Triggers on "read this - file from the repo", "what's in this directory", "show me the repo tree", "create/update a file - in Gitea", "commit this file to the branch", "delete this file from the repo" — even when the - user doesn't say "Gitea" explicitly, as long as the target is a Gitea-hosted repository. Do not - use for local filesystem file operations (use Read/Write/Edit), for branch or commit history - (use gitea-branches), or for opening a pull request around a file change (use gitea-prs after - the file write completes here). + Use when reading or writing files in a Gitea repository rather than on the local filesystem — + read, list, walk the tree, create, update, or delete — even when the user does not say "Gitea". + Not commit history -> `gitea-branches`. Not pull requests -> `gitea-prs`. compatibility: Requires the Gitea MCP server configured with a token scoped to at least write:repository. Tested with a token holding write:issue + write:repository; write:issue @@ -28,29 +22,27 @@ allowed-tools: mcp__gitea__get_file_contents mcp__gitea__get_dir_contents mcp__g ## Gotchas -- **A 404 from any read call may actually be a 403 in disguise.** `get_file_contents`, `get_dir_contents`, and `get_repository_tree` all gate on `write:repository` scope, not just read access — some Gitea endpoints return 404 instead of 403 when the token's scope is insufficient, to avoid leaking whether the resource exists. If a read fails with 404 on a path you're confident is correct, check the token's configured scopes before concluding the file or directory doesn't exist. -- **SHA is the concurrency token for every write — and it lives at the top level of `get_file_contents`'s response, not nested under `content`.** `create_or_update_file` without `sha` is always treated as a *create*: if the path already exists, Gitea returns HTTP 409. `delete_file` has no optional path at all — omitting `sha` returns HTTP 422. The safe sequence for any update or delete is always: call `get_file_contents` first, read the top-level `sha` field, then pass that exact value to the write call. Never guess or reuse a stale SHA — a mismatched SHA is rejected the same as a missing one. -- **A write can also fail because the branch requires signed commits — a separate failure mode from a bad SHA.** `create_or_update_file` and `delete_file` create commits server-side via a bare API token call with no 2FA/PGP context. If the target branch's protection rule requires signed commits, Gitea rejects the write outright — surfaced as a generic 403 or 422, not an error naming "signed commit required," and reads against that same branch keep succeeding right up until you try to write. When a write fails without a clean 409 (missing/stale SHA) or 404 (bad path) explanation, check whether the branch's protection rule requires signed commits before assuming the SHA is wrong and retrying. -- **A large `create_or_update_file` payload can hit a reverse-proxy 413 that has nothing to do with Gitea.** `content` is base64-encoded, which inflates the payload ~33% over the raw file size; a 413 is commonly a reverse-proxy body-size limit in front of the Gitea instance, not a Gitea-side rejection. No amount of retrying, or changing the SHA, path, or branch, will fix it — it needs the proxy's config raised, which is outside this skill's or the calling agent's control. Surface that distinction to the user instead of retrying the same call. -- **`get_dir_contents` and `get_repository_tree` are not SHA sources for a specific file's write.** `get_dir_contents` entries carry no `sha` at all. `get_repository_tree` entries do carry a `sha` (a blob/tree hash), but fetching it means an extra round trip with no content — `get_file_contents` is the canonical path since it returns the decoded content and the write-ready `sha` in one call. -- **`owner` and `repo` are always caller-supplied inputs, never resolved here.** This skill doesn't infer them from a git remote. If invoked directly by a human, ask for them if not stated. If invoked by `gitea-workflow` or an orchestrating agent, expect them to already be resolved and passed in. -- **Direct commits to a branch are a first-class action, not a workaround.** Gitea's own web UI defaults to editing files directly against a branch — `create_or_update_file`/`delete_file` used that way is normal, not an API escape hatch to avoid. The SHA-currency requirement above is the actual risk to manage, not the act of committing directly. -- **`ref` (reads) vs. `branch_name` (writes) are different parameters for the same concept.** `get_file_contents`, `get_dir_contents`, and `get_repository_tree` (as `tree_sha`) all accept a branch name, tag, or commit SHA to select what to read. `create_or_update_file` and `delete_file` instead take `branch_name` — the branch the commit lands on. Don't conflate the two when chaining a read into a write. -- **Content is base64.** `create_or_update_file`'s `content` parameter is base64-encoded file content, not raw text — encode before calling. `get_file_contents`'s response content is likewise base64-encoded (decode after reading), unless `withLines: true` is passed for a numbered-line view. +- **A 404 may mean an under-scoped token, not a missing path.** Every tool here gates on `write:repository`, and Gitea masks insufficient scope as 404. Check scopes first. +- **Reads take `ref`, writes take `branch_name`.** One concept, two parameter names — chaining a read into a write drops the branch if you carry the wrong key. +- **`content` is base64 both ways.** Encode before a write, decode after a read; `withLines: true` returns numbered lines. -## Reading +## Inputs -- **Single file:** `get_file_contents(owner, repo, ref, path)`. Pass `withLines: true` only when you need line numbers for referencing specific lines (e.g. quoting a snippet back to the user); omit it for a normal content fetch. -- **One directory level:** `get_dir_contents(owner, repo, ref, path)` — returns immediate entries only (name, path, type, size), no recursion, no SHA, no content. -- **Whole tree:** `get_repository_tree(owner, repo, tree_sha, recursive)` — `tree_sha` accepts a SHA, branch, or tag name despite the name. Set `recursive: true` to walk subdirectories in one call. Response includes `truncated: true` when a page doesn't hold every entry — page through with `page`/`per_page` (default `page: 1`, `per_page: 30`) until you get fewer results than `per_page`. +`owner`, `repo` and the target branch are caller-supplied. This skill never infers them from a git remote: ask the human when they are not stated, and expect an orchestrating caller to have resolved them already. -## Writing +## Dispatch -- **Creating a new file:** call `create_or_update_file(owner, repo, path, content, message, branch_name)` with `sha` omitted entirely. -- **Updating an existing file:** call `get_file_contents(owner, repo, ref: branch_name, path)` first, take the top-level `sha`, then call `create_or_update_file(..., sha: )`. -- **Deleting a file:** call `get_file_contents` first the same way, then `delete_file(owner, repo, path, message, branch_name, sha: )` — `sha` is required, no create-style fallback exists. -- **Creating a new branch as part of the write:** pass `new_branch_name` on `create_or_update_file` to branch off before the commit lands, instead of calling a separate branch-creation step. +| Condition | Flow | Reference | +|---|---|---| +| Read one file, list one directory level, or walk the repository tree | Read | `references/reading.md` | +| Create, update, or delete a file | Write | `references/writing.md` | -If the change needs review before merging, or targets a protected branch, hand off to `gitea-prs` after the write lands here to open the pull request — this skill's scope ends at the commit. +If the request only inspects repository contents, read `references/reading.md` — it carries the three read tools, their pagination behaviour, and why neither a directory listing nor a tree entry supplies the SHA a write needs. -If you need the full multi-call sequence rather than the single-call summary above — e.g. branching off as part of a file push ahead of opening a PR, or recovering a SHA you didn't capture earlier — read `references/examples.md`. +If the request creates, updates or deletes a file, read `references/writing.md` — it carries the SHA-first sequence every update and delete depends on, the worked multi-call sequence, and how to triage a write that fails. + +A request that reads and then writes runs both flows in that order: fetch the file first, then write with the SHA that call returned. + +## Handoff + +Scope ends at the commit. Gitea's own web UI edits files directly against a branch, so committing straight to a branch is the normal path rather than an escape hatch — hand off to `gitea-prs` when the change needs review before merging or the target branch is protected, not by default. diff --git a/plugins/gitea/skills/gitea-files/references/examples.md b/plugins/gitea/skills/gitea-files/references/examples.md deleted file mode 100644 index c67af6a..0000000 --- a/plugins/gitea/skills/gitea-files/references/examples.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -source_keys: - - gitea-mcp-repo - - gitea-mcp-slim-go ---- - -# Canonical call sequences - -## Push a file to a new branch, then open a PR - -``` -1. get_repository_tree or get_file_contents on the base branch — only needed if the - new file is actually replacing an existing one; skip for a brand-new path. - -2. create_or_update_file - owner, repo - path: "docs/example.md" - content: "" - message: "docs: add example" - branch_name: "main" - new_branch_name: "feat/add-example" ← branches off before the commit lands - (sha omitted — this is a new file) - -3. Hand off to gitea-prs to open a PR from "feat/add-example" into "main". -``` - -`new_branch_name` on `create_or_update_file` replaces a separate branch-creation call — the branch is created and the commit lands on it in one step. - -## Update a file when you don't already have its SHA - -SHA is mandatory for updates. If it wasn't captured earlier in the conversation: - -``` -1. get_file_contents - owner, repo - ref: "main" - path: "docs/example.md" - → read the top-level `sha` field (not content.sha) - -2. create_or_update_file - owner, repo - path: "docs/example.md" - content: "" - message: "docs: update example" - branch_name: "main" - sha: "" -``` - -Do not guess or omit the SHA — the write either fails (409 on create-path fallback) or is rejected outright. - -## Delete a file - -Same SHA-first pattern, no fallback path: - -``` -1. get_file_contents owner, repo, ref: "main", path: "docs/old-example.md" - → read the top-level `sha` field - -2. delete_file - owner, repo - path: "docs/old-example.md" - message: "docs: remove old example" - branch_name: "main" - sha: "" -``` diff --git a/plugins/gitea/skills/gitea-files/references/reading.md b/plugins/gitea/skills/gitea-files/references/reading.md new file mode 100644 index 0000000..ca1847a --- /dev/null +++ b/plugins/gitea/skills/gitea-files/references/reading.md @@ -0,0 +1,46 @@ +--- +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go +--- + +# Reading files, directories and trees + +All three read calls select what to read with `ref` — a branch name, tag, or commit SHA. On +`get_repository_tree` the same value goes in `tree_sha` despite the name. + +## Single file + +`get_file_contents(owner, repo, ref, path)`. + +The response carries the file's `sha` at the **top level**, not nested under `content`. That field +is the write-ready SHA, so capture it whenever a write may follow. Content comes back +base64-encoded — decode it. Pass `withLines: true` only when you need numbered lines to quote +specific lines back to the user; omit it for a normal content fetch. + +## One directory level + +`get_dir_contents(owner, repo, ref, path)` returns the immediate entries only — name, path, type, +size. No recursion, no content, no `sha`. + +## Whole repository tree + +`get_repository_tree(owner, repo, tree_sha, recursive)`. Set `recursive: true` to walk +subdirectories in one call. + +The response sets `truncated: true` when one page does not hold every entry. Page through with +`page`/`per_page` (defaults `1` and `30`) until a page returns fewer entries than `per_page`. + +## Neither listing is a SHA source for a write + +`get_dir_contents` entries carry no `sha` at all. `get_repository_tree` entries do carry a blob or +tree hash, but reaching it costs an extra round trip and returns no content. `get_file_contents` is +the canonical path for a write's SHA: one call returns the decoded content and the write-ready +`sha` together. + +## A 404 that is really a 403 + +These reads gate on `write:repository`, not on read access alone, and some Gitea endpoints answer +an under-scoped token with 404 instead of 403 so they do not leak whether the resource exists. A +404 on a path you are confident about is a scope problem until proven otherwise — check the token's +configured scopes before concluding the file or directory does not exist. diff --git a/plugins/gitea/skills/gitea-files/references/sources.md b/plugins/gitea/skills/gitea-files/references/sources.md index 5b15165..756778d 100644 --- a/plugins/gitea/skills/gitea-files/references/sources.md +++ b/plugins/gitea/skills/gitea-files/references/sources.md @@ -8,9 +8,10 @@ - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md -**Contributing files:** -- SKILL.md (Gotchas, Reading, Writing — tool parameters and SHA/concurrency behavior, cross-checked live against the deployed MCP tool schemas via ToolSearch) -- references/examples.md (canonical call sequences) +**Contributing files:** (tool parameters and SHA/concurrency behavior cross-checked live against the deployed MCP tool schemas via ToolSearch) +- SKILL.md (Gotchas — cross-flow parameter and encoding traps; Dispatch) +- references/reading.md (read-tool parameters, `ref`/`tree_sha` selection, tree pagination) +- references/writing.md (write-tool parameters, SHA/concurrency behavior, canonical call sequences, failed-write triage) ## gitea-mcp-slim-go @@ -21,8 +22,9 @@ - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md **Contributing files:** -- SKILL.md (Gotchas — top-level `sha` field location, `get_dir_contents`/`get_repository_tree` not being usable SHA sources for a file write) -- references/examples.md (SHA-first update/delete sequences) +- SKILL.md (Gotchas — base64 response encoding) +- references/reading.md (top-level `sha` field location, `get_dir_contents`/`get_repository_tree` not being usable SHA sources for a file write) +- references/writing.md (SHA-first update/delete sequences) ## context7-websites-gitea @@ -33,7 +35,7 @@ - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md **Contributing files:** -- SKILL.md (Gotchas — "Direct commits to a branch are a first-class action, not a workaround") +- SKILL.md (Handoff — committing straight to a branch is the normal path, not an escape hatch) ## context7-gitea-tea-cli diff --git a/plugins/gitea/skills/gitea-files/references/writing.md b/plugins/gitea/skills/gitea-files/references/writing.md new file mode 100644 index 0000000..c8c96fd --- /dev/null +++ b/plugins/gitea/skills/gitea-files/references/writing.md @@ -0,0 +1,76 @@ +--- +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go +--- + +# Creating, updating and deleting files + +`sha` is the optimistic-concurrency token for every write, and it comes from +`get_file_contents`'s **top-level** `sha` field — never from `content.sha`, a directory listing, or +a tree entry. Do not guess or reuse a stale value: a mismatched SHA is rejected exactly like a +missing one. + +`content` is base64-encoded, and the branch the commit lands on is `branch_name`, not `ref`. + +## Create a new file + +Call `create_or_update_file(owner, repo, path, content, message, branch_name)` with `sha` omitted +entirely. An omitted `sha` always means *create*, so the call returns HTTP 409 if the path already +exists. + +To branch off as part of the same write, pass `new_branch_name`: the branch is created and the +commit lands on it in one call, replacing a separate branch-creation step. + +## Update an existing file + +1. `get_file_contents(owner, repo, ref: , path)` → read the top-level `sha`. +2. `create_or_update_file(owner, repo, path, content, message, branch_name, sha: )`. + +## Delete a file + +Same SHA-first pattern, with no create-style fallback — `delete_file` without `sha` returns +HTTP 422. + +1. `get_file_contents(owner, repo, ref: , path)` → read the top-level `sha`. +2. `delete_file(owner, repo, path, message, branch_name, sha: )`. + +## Worked sequence — new file on a new branch, then a PR + +``` +1. create_or_update_file + owner, repo + path: "docs/example.md" + content: "" + message: "docs: add example" + branch_name: "main" + new_branch_name: "feat/add-example" ← branches off before the commit lands + (sha omitted — this is a new file) + +2. Hand off to gitea-prs to open a PR from "feat/add-example" into "main". +``` + +Step 1 needs no preceding read: a brand-new path has no SHA. Fetch the current file first only +when the write replaces an existing one. + +## Triaging a failed write + +| Symptom | Cause | Action | +|---|---|---| +| HTTP 409 | `sha` omitted on a path that already exists | Fetch the current SHA, retry as an update | +| HTTP 422 | `sha` missing or stale | Re-fetch the SHA immediately before the write | +| 403 or 422 with no SHA explanation | Branch protection requires signed commits | Stop and report | +| HTTP 413 | Reverse-proxy body limit in front of Gitea | Report; retrying cannot fix it | +| HTTP 404 | Wrong path, or a token without `write:repository` | Verify the path, then the token's scopes | + +**Signed commits.** These writes create commits server-side from a bare API token with no 2FA or +PGP context. If the target branch's protection rule requires signed commits, Gitea rejects the +write as a generic 403 or 422 that never names signing, and reads against that same branch keep +succeeding right up until the write. When a write fails without a clean 409 or 404 explanation, +check the branch's protection rule before assuming the SHA is wrong and retrying. + +**Payload size.** base64 inflates `content` roughly 33% over the raw file size, and a 413 is +usually a reverse-proxy body-size limit in front of the Gitea instance rather than a Gitea-side +rejection. No amount of retrying, or changing the SHA, path, or branch, will fix it — the proxy's +config has to be raised, which is outside this skill's control. Surface that distinction instead +of retrying the same call. diff --git a/plugins/gitea/skills/gitea-issues/README.md b/plugins/gitea/skills/gitea-issues/README.md index 7d83306..adf34d8 100644 --- a/plugins/gitea/skills/gitea-issues/README.md +++ b/plugins/gitea/skills/gitea-issues/README.md @@ -18,9 +18,19 @@ the blocked `get_me` scope, and the "Depends on #N" dependency-linking conventio Requires a Gitea MCP server configured with a token holding `write:issue` + `write:repository`. Requires a git remote named `origin` pointing at the Gitea instance, unless an orchestrating caller -(e.g. `gitea-workflow`) already resolved `owner`/`repo` for you. Label and milestone management -(creating/editing a label, creating/closing a milestone) is out of scope here — that's -`gitea-labels-milestones`, which this skill composes rather than duplicates. +(e.g. `gitea-workflow`) already resolved `owner`/`repo` for you. + +## How it composes + +This skill composes `gitea-labels-milestones` for *all* label inference, label-name-to-ID +resolution, and milestone lookup, rather than duplicating that taxonomy or its resolution logic — +see `references/enrichments.md` for the call protocol. Managing the label and milestone definitions +themselves (create/edit/delete a label, create/close a milestone) is out of scope here and goes to +`gitea-labels-milestones` directly. + +One boundary the description does not spend characters on, because it was never going to win an +issue request: local git branch or commit work belongs to `gitea-branches` (Gitea-side) or +`git-branches` (working copy). ## Usage @@ -28,8 +38,8 @@ Requires a git remote named `origin` pointing at the Gitea instance, unless an o /gitea-issues ``` -Describe your task: list issues, create one, get/comment/close a specific issue number, or search -across repos. See `SKILL.md`'s dispatch table for the full set of recognized invocations. +Describe your task: list issues, create one, get/comment/close/label a specific issue number, or +search across repos. See `SKILL.md`'s dispatch table for the full set of recognized invocations. ## Files diff --git a/plugins/gitea/skills/gitea-issues/SKILL.md b/plugins/gitea/skills/gitea-issues/SKILL.md index 8cfc6fe..b168d4a 100644 --- a/plugins/gitea/skills/gitea-issues/SKILL.md +++ b/plugins/gitea/skills/gitea-issues/SKILL.md @@ -2,15 +2,9 @@ name: gitea-issues description: > - Use when reading or writing Gitea issues: listing repo issues, getting a single issue's details/ - comments/labels, creating an issue, updating its state, adding or editing comments, applying - labels via issue_write, or searching issues/PRs across repositories. Triggers on "create an - issue", "what issues are open", "get issue #N", "close issue #N", "comment on issue #N", "search - issues for X" — even when the user doesn't say "Gitea" explicitly. Composes gitea-labels- - milestones for all label inference/resolution and milestone lookup — do not use this skill to - manage label or milestone definitions themselves (create/edit/delete a label, create/close a - milestone), that's gitea-labels-milestones directly. Do not use for pull requests (use gitea-prs) - or for local git branch/commit work (use gitea-branches or git-branches). + Use when reading or writing Gitea issues — even when the user does not say "Gitea". + Not pull requests -> `gitea-prs`. + Not label or milestone definitions -> `gitea-labels-milestones`. compatibility: Requires Gitea MCP server configured with write:issue and write:repository token scopes. Requires git remote "origin" pointing to the Gitea instance for owner/repo resolution @@ -19,7 +13,7 @@ compatibility: Requires Gitea MCP server configured with write:issue and write:r metadata: category: integration - version: "0.1.0" + version: "0.1.3" source_keys: - gitea-mcp-repo - gitea-mcp-slim-go @@ -31,75 +25,49 @@ allowed-tools: Bash mcp__gitea__list_issues mcp__gitea__issue_read mcp__gitea__i ## Gotchas -- **`list_issues` has no `type` or `milestones` parameter — despite `api-reference.md` documenting both.** The live MCP schema (re-verified via `ToolSearch` at authoring time — see `references/sources.md`) only accepts `owner`, `repo` (required), `state` (default `"all"`), `labels` (array of label *names*), `since`, `before` (ISO 8601), `page`, `per_page` (default 30). This tool provides no way to filter issues-vs-PRs or by milestone. Since issues and PRs share one number space, `list_issues` results can include PR entries with no client-side filter to exclude them. If you need to know whether a specific number is a PR, call `issue_read method: "get"` and check `is_pull` — that field only appears on the single-item response, never in a list item. This exact drift (a prior skill trusted the research doc's `type` param and broke) is why this skill's reference files were re-verified live rather than copied from `api-reference.md`. -- **`search_issues` does have a working `type` filter** (`"issues"` | `"pulls"`) — unlike `list_issues`. Its `labels` parameter is also shaped differently: a comma-separated string, not an array of names. -- **Labels are numeric IDs on write, name strings on read.** `issue_write`'s `labels` parameter (used by `add_labels`/`replace_labels`) takes IDs. `list_issues`/`issue_read` return names. Never resolve this yourself — compose `gitea-labels-milestones` (see `references/enrichments.md`) to get IDs. -- **Milestone on `issue_read` is `{id, title}`** — an object, not a bare string. This skill only ever needs the `id`. (The bare-title-string case only happens on the PR side, which is `gitea-prs`' problem, not this skill's.) -- **Closing-keyword auto-close behavior is plausible but unconfirmed in our research docs.** Our research docs confirm Gitea does NOT auto-close an issue on a plain PR merge (unlike GitHub) — closing keywords like `Fixes #N`/`Closes #N` in a commit message are not documented one way or the other. After a PR merges (a `gitea-prs` operation), always re-check the issue's state here via `issue_read method: "get"` before deciding whether to close it manually — closing an already-closed issue is a harmless no-op, but don't assume a manual close is always needed. -- **Pagination is manual.** `list_issues` and `search_issues` return one page at a time. Iterate `page: 1, 2, ...` until the returned count is less than `per_page`. -- **HTTP 404 may actually mean 403.** Gitea hides permission errors as not-found. If a call 404s unexpectedly, verify the token holds `write:issue` scope (see `references/issues.md`'s Token scope note) before concluding the issue doesn't exist. +- **`list_issues` returns PRs too.** It has no `type` filter and both share one repo number space. `is_pull` appears only on `issue_read method: "get"`, never on a list item — check it there before treating a number as an issue. +- **Label IDs and names are not interchangeable.** `issue_write` takes numeric IDs only; `list_issues` and `search_issues` filter by name; `issue_read "get"` returns names but `"get_labels"` returns full objects with IDs. Resolve via `gitea-labels-milestones` unless the caller named exact labels. +- **A merged PR leaves its issue open.** Gitea does not auto-close on merge the way GitHub does. Re-read the issue's state after a merge before closing it manually. +- **A 404 may really be a 403.** Gitea hides permission errors as not-found — check the token's `write:issue` scope before concluding the issue does not exist. ## Step 1 — Resolve owner and repo -Before any tool call, extract `owner` and `repo` from the git remote (skip this if an orchestrating caller already passed them in): +An orchestrating caller may pass `owner` and `repo` in already; if so, skip this. The `search` row is cross-repository and needs only a query, so it skips this too. Otherwise, before any tool call: ```bash git remote get-url origin ``` -If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." +If origin is unset or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." ## Step 2 — Dispatch -| Invocation | Action | -|---|---| -| `/gitea-issues` or `/gitea-issues list` | List issues (optional state filter) | -| `/gitea-issues create` | Create an issue from conversation context — infers labels, checks milestone fit, applies a configured default assignee if set | -| `/gitea-issues ` | Get issue details (flag it as a PR if `is_pull: true`) | -| `/gitea-issues comments` | Get an issue's comments | -| `/gitea-issues close ` | Close an issue | -| `/gitea-issues comment ` | Add a comment from conversation context | -| `/gitea-issues search ` | Cross-repo search via `search_issues` | +One invocation takes one row. Read only the reference(s) that row names — the call signatures were verified against the live MCP schema and differ from the published API docs in ways the body does not restate. -For full parameter detail on `list_issues`/`issue_read`/`issue_write`, read `references/issues.md`. For `search_issues`, read `references/search.md`. For the create-flow enrichments (label inference, milestone assignment, assignee workaround, dependency-linking), read `references/enrichments.md`. +| Invocation | Flow | Read | +|---|---|---| +| `/gitea-issues` or `/gitea-issues list` | List issues, optionally filtered by state | `references/issues.md` | +| `/gitea-issues ` | Get one issue, routing to `gitea-prs` when the number turns out to be a PR | `references/issues.md` | +| `/gitea-issues comments` | Get an issue's comments | `references/issues.md` | +| `/gitea-issues labels` | Get an issue's labels as full objects, IDs included | `references/issues.md` | +| `/gitea-issues close ` | Close an issue by updating its state | `references/issues.md` | +| `/gitea-issues comment ` | Add a comment drawn from conversation context, never one invented to fill the gap — a comment on a live issue is not cheap to undo | `references/issues.md` | +| `/gitea-issues label ` | Apply, replace or remove labels using IDs resolved by `gitea-labels-milestones` | `references/issues.md` | +| `/gitea-issues create` | Create an issue — Step 3 first | `references/enrichments.md`, then `references/issues.md` | +| `/gitea-issues search ` | Cross-repo search, narrowed by owner, state, type or labels | `references/search.md` | -## Step 3 — Execute +## Step 3 — Create -### list (default) +Only the create flow reaches this step; every other row goes straight to its reference. -Call `list_issues owner: repo: state: <"open"|"closed"|"all", default "all">`. Remember: results may include PR entries (see Gotchas) — if the caller needs issues only, this tool cannot filter that server-side; note the limitation rather than silently mislabeling PR entries as issues. - -### create - -1. Extract `title` and `body` from conversation context (the most recent task, bug description, or explicit statement). Fall back to an empty body if nothing is available. -2. Run the enrichment sequence in `references/enrichments.md`: infer labels (composing `gitea-labels-milestones`), check for a clearly-fitting open milestone (composing the same skill), and check for a configured default assignee. -3. Call `issue_write method: "create" owner: repo: title: body: <body> labels: [<resolved IDs, or omit>] milestone: <resolved ID, or omit> assignees: [<default login, or omit>]`. -4. Fire immediately — no confirmation step for the create itself. - -### `<N>` (get) - -Call `issue_read method: "get" owner: <owner> repo: <repo> issue_number: <N>`. If `is_pull: true`, report that this number is actually a PR and suggest `gitea-prs` for full detail. - -### `<N> comments` - -Call `issue_read method: "get_comments" owner: <owner> repo: <repo> issue_number: <N>`. - -### close `<N>` - -Call `issue_write method: "update" owner: <owner> repo: <repo> issue_number: <N> state: "closed"`. No `method: "close"` exists. - -### comment `<N>` - -Extract the comment body from conversation context (same sourcing as create). Call `issue_write method: "add_comment" owner: <owner> repo: <repo> issue_number: <N> body: <body>`. - -### search `<query>` - -Call `search_issues query: <query>`, adding `owner`, `state`, `type`, or `labels` filters if the request narrows scope (e.g. "search open PRs for X" → `type: "pulls" state: "open"`). +1. Take `title` and `body` from conversation context — the most recent task, bug report, or explicit statement. An empty body is an acceptable fallback, an invented one is not. +2. Run the enrichments in `references/enrichments.md`, then create with the resolved IDs per `references/issues.md`. Omitting a parameter always beats guessing its value — a wrong milestone or assignee is harder to notice than a missing one. +3. Fire immediately, with no confirmation step. A create is cheap to undo by closing, so a gate here only costs a round trip. ## Step 4 — Report -For reads: a compact table or numbered list — number, title, state, labels, milestone. +Reads: a compact table or numbered list — number, title, state, labels, milestone. -For writes: confirm what was created/updated with the issue number and URL if returned. +Writes: what was created or updated, with the issue number and the URL when one is returned. -For errors: surface the HTTP code and message; check token scope per the Gotchas if a 404 looks wrong. +Errors: the HTTP code and message as returned, without paraphrasing either. diff --git a/plugins/gitea/skills/gitea-issues/references/enrichments.md b/plugins/gitea/skills/gitea-issues/references/enrichments.md index 09ae4ac..2977587 100644 --- a/plugins/gitea/skills/gitea-issues/references/enrichments.md +++ b/plugins/gitea/skills/gitea-issues/references/enrichments.md @@ -19,8 +19,7 @@ plausibly fits). reference files directly by path. A plugin install copies each skill's directory into an isolated cache — any file path that leaves this skill's own directory breaks post-install. Instead, compose `gitea-labels-milestones` as a skill: describe the task to it (its own `SKILL.md` and description -trigger it) and consume the resolved IDs it returns. This mirrors how `gitea-labels-milestones`'s -own description already frames the relationship ("`gitea-issues` and `gitea-prs` both compose it"). +trigger it) and consume the resolved IDs it returns. ## 1. Label inference diff --git a/plugins/gitea/skills/gitea-issues/references/sources.md b/plugins/gitea/skills/gitea-issues/references/sources.md index 65b6516..904be31 100644 --- a/plugins/gitea/skills/gitea-issues/references/sources.md +++ b/plugins/gitea/skills/gitea-issues/references/sources.md @@ -5,8 +5,8 @@ signatures in `references/issues.md` and `references/search.md` were re-verified `ToolSearch` against the deployed `gitea-mcp` server at authoring time — they are not copied verbatim from `api-reference.md`. This resolves issue #6 comment #849's root-cause finding that a prior skill was authored from API docs that had drifted from the actual MCP tool schema; the live -check caught exactly this drift on `list_issues` (see SKILL.md Gotchas — the research doc documents -a `type` and a `milestones` parameter that do not exist on the deployed server). +check caught exactly this drift on `list_issues` (see `references/issues.md` — the research doc +documents a `type` and a `milestones` parameter that do not exist on the deployed server). ## gitea-mcp-repo diff --git a/plugins/gitea/skills/gitea-labels-milestones/README.md b/plugins/gitea/skills/gitea-labels-milestones/README.md index 30fb502..d509c71 100644 --- a/plugins/gitea/skills/gitea-labels-milestones/README.md +++ b/plugins/gitea/skills/gitea-labels-milestones/README.md @@ -4,7 +4,13 @@ Read and write Gitea labels and milestones, and resolve label/milestone identity ## What it does -This skill handles label and milestone CRUD (`label_read`/`label_write`, `milestone_read`/`milestone_write`) — listing repo or org labels, creating/editing/deleting a label, resolving a label name to the numeric ID required for any write, and listing/creating/updating/closing/deleting a milestone. It also owns label inference: mapping conversation context (bug report, feature request, urgency language) to this repo's `Kind/*`/`Priority/*`/`Status/*` taxonomy. It is a cross-cutting shared skill composed by `gitea-issues` and `gitea-prs`, which call into it for label/milestone resolution before their own `issue_write`/`pull_request_write` calls apply the resolved IDs. +This skill handles label and milestone CRUD (`label_read`/`label_write`, `milestone_read`/`milestone_write`) — listing repo or org labels, creating/editing/deleting a label, resolving a label name to the numeric ID required to apply it to an issue or PR, and listing/creating/updating/closing/deleting a milestone. It also owns label inference: mapping conversation context (bug report, feature request, urgency language) to this repo's `Kind/*`/`Priority/*`/`Status/*` taxonomy. + +## Composition + +This is a cross-cutting shared skill. `gitea-issues` and `gitea-prs` both compose it whenever they need to apply a label or assign a milestone, rather than duplicating label/milestone logic: they call in for name/title → ID resolution, then their own `issue_write`/`pull_request_write` calls apply the resolved IDs. The split is deliberate — identity resolution lives here once, and the write that attaches an ID to a specific issue or PR lives with the skill that owns that object. + +That relationship is documented here rather than in the skill description, which is preloaded into every session and carries routing information only: an agent reaches this skill because the user asked about labels or milestones, not because two other skills call it. ## Usage diff --git a/plugins/gitea/skills/gitea-labels-milestones/SKILL.md b/plugins/gitea/skills/gitea-labels-milestones/SKILL.md index b9844dc..67fd8d8 100644 --- a/plugins/gitea/skills/gitea-labels-milestones/SKILL.md +++ b/plugins/gitea/skills/gitea-labels-milestones/SKILL.md @@ -2,15 +2,9 @@ name: gitea-labels-milestones description: > - Use when reading or writing Gitea labels or milestones — listing repo/org labels, creating, - editing, or deleting a label, resolving label names to the numeric IDs required for applying - them to an issue or PR, or listing, creating, updating, closing, or deleting a milestone. This is - a cross-cutting shared skill: `gitea-issues` and `gitea-prs` both compose it whenever they need to - apply labels or assign a milestone, rather than duplicating label/milestone logic. Also use for - label inference — mapping a bug report, feature request, or urgency signal in conversation - context to the repo's `Kind/*`/`Priority/*`/`Status/*` label taxonomy. Do not use for applying - already-resolved label IDs or milestone IDs to a specific issue or PR — that write goes through - `issue_write`/`pull_request_write` in `gitea-issues`/`gitea-prs`, not here. + Use when reading or writing Gitea labels or milestones — resolve names to IDs, or infer + labels — even when the user does not say "Gitea". + Not applying them to an issue -> `gitea-issues`. Not to a PR -> `gitea-prs`. compatibility: Requires Gitea MCP server configured with write:issue and write:repository token scopes. @@ -21,23 +15,18 @@ metadata: - gitea-mcp-slim-go - context7-websites-gitea - context7-gitea-tea-cli - version: "0.1.1" + version: "0.1.4" allowed-tools: Bash mcp__gitea__label_read mcp__gitea__label_write mcp__gitea__milestone_read mcp__gitea__milestone_write --- ## Gotchas -- **Label writes take IDs, reads return names.** `label_read` is the only tool that returns full label objects (`id`, `name`, `color`, `description`). Issue/PR responses slim labels down to name strings. Before any label is applied to an issue or PR (in `gitea-issues`/`gitea-prs`), resolve names → IDs here via `label_read method: "list_repo_labels"` — never pass a name string where an ID is expected. -- **Milestones are referenced by ID everywhere, never by title.** `milestone_write` update/delete take `id`. The one place titles show up as the sole handle is the `pull_request_read` response (see next gotcha). -- **Milestone representation differs between issues and PRs.** `issue_read` returns `milestone: {id, title}` — an object. `pull_request_read` returns `milestone: "title string"` — title only, no ID. You cannot recover a milestone ID from a PR response directly; call `milestone_read method: "list"` and match by title instead. -- **Repo labels and org labels are separate pools, never mixed in one call.** `label_read`/`label_write` take either `owner`+`repo` (repo-scoped methods) or `org` (org-scoped methods) — passing both or neither for a given method is a caller error, not something the schema enforces for you. Repo and org labels can both apply to the same issue, but you list/create/edit them through different method values. -- **`milestone_write` accepts `"update"` and `"edit"` as the same operation.** Both method values map to the identical update call. Prefer `"update"` for consistency with `issue_write`/`pull_request_write`. -- **`exclusive` is documented as an org-labels-only flag — it isn't what enforces exclusivity here.** Gitea's docs scope the settable/server-enforced `exclusive` flag to org labels only, and the live `label_write` schema for `create_repo_label`/`edit_repo_label` doesn't document accepting it at all. This repo's `Kind/*`, `Priority/*`, `Status/*` groups still behave as one-label-per-scope, but that's a manually-enforced convention this skill implements client-side, not a guaranteed server behavior for repo labels: applying a new label within a scope (e.g. `Priority/High`) must replace any existing label in that same scope, not add alongside it, and nothing on the server enforces that for you. Label inference (see `references/label-inference.md`) must respect this — replace, don't stack. -- **Pagination is manual on every list call.** `label_read` and `milestone_read` both default to `per_page: 30`. Iterate `page: 1, 2, ...` until the result count is less than `per_page` — there is no cursor or auto-pagination. -- **Schema requiredness differs between the two tool families.** `milestone_read`/`milestone_write` have `owner` and `repo` as hard-required parameters (the call fails validation without them). `label_read`/`label_write` only hard-require `method` — `owner`/`repo`/`org` are functionally required per method but not schema-enforced, so passing none produces a runtime error from Gitea, not a client-side validation error. +- **Applying a label takes a numeric ID, but issue/PR responses slim labels down to name strings.** An issue's existing labels yield no IDs — resolve name → ID with `label_read`. +- **`pull_request_read` returns `milestone` as a bare title string** where `issue_read` returns `{id, title}` — recover the milestone's ID by listing milestones and matching the title. +- **`Kind/*`/`Priority/*`/`Status/*` exclusivity is a client-side convention.** `exclusive` is an org-labels-only flag, so applying a label in such a scope must replace the one already there, not stack on it. -## Step 1 — Resolve owner and repo +## Step 1 — Resolve owner, repo and org Before any tool call, extract `owner` and `repo` from the git remote (skip this if an orchestrating caller already passed them in): @@ -47,10 +36,13 @@ git remote get-url origin If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." +The `*_org_label*` methods take `org`, not `owner`/`repo`. Pass that same `owner` as `org` — it is the org name whenever the owner is an organisation, and the remote URL does not say whether it is one. So let the call itself decide: a failure means the owner is a user account with no org label pool, which is an answer, not an error to report. + ## Step 2 — Dispatch | Task | Tool | method | |---|---|---| +| Resolve a label name to its ID | `label_read` | `"list_repo_labels"`, then `"list_org_labels"` | | List repo labels | `label_read` | `"list_repo_labels"` | | Get one repo label by ID | `label_read` | `"get_repo_label"` | | List org labels | `label_read` | `"list_org_labels"` | @@ -63,6 +55,6 @@ If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea r | Update / close a milestone | `milestone_write` | `"update"` | | Delete a milestone | `milestone_write` | `"delete"` | -For full parameter detail and step-by-step call sequences, read `references/labels.md` (label operations) or `references/milestones.md` (milestone operations). For mapping conversation context to a label to apply, read `references/label-inference.md`. +Every list method paginates manually — `per_page` defaults to 30, so iterate `page: 1, 2, ...` until a page returns fewer results than `per_page`. A truncated list silently breaks name → ID resolution. -Applying resolved label IDs or a milestone ID to a specific issue or PR is out of scope here — that's `issue_write`/`pull_request_write` in the composing skill (`gitea-issues`/`gitea-prs`). +If the task is a label operation, read `references/labels.md`; if a milestone operation, read `references/milestones.md`. If the label to apply has to be derived from conversation context rather than named, read `references/label-inference.md`. diff --git a/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md b/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md index ba4665b..0f7ddab 100644 --- a/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md +++ b/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md @@ -57,7 +57,11 @@ scope applied at once. 1. Read the conversation context (issue/PR title, body, or the triggering discussion) for the signals above. 2. Call `label_read method: "list_repo_labels"` (see `references/labels.md`) to get the current - label set with IDs — inference must never guess an ID, only a name, then resolve it. + label set with IDs — inference must never guess an ID, only a name, then resolve it. Because + `exclusive` is an org-labels-only flag, this taxonomy plausibly lives at org scope too: for any + inferred name absent from the repo pool, also call `label_read method: "list_org_labels"` with + `org` set to the repo's `owner` before treating it as unresolved. A failure there means the owner + is a user account, not an organisation, so no org pool exists and the name is genuinely absent. 3. Match inferred label names against the resolved list (case-insensitive). If a scope group already has a different label applied on the target and a new one is inferred for that same scope, plan to replace rather than add (see above). diff --git a/plugins/gitea/skills/gitea-labels-milestones/references/labels.md b/plugins/gitea/skills/gitea-labels-milestones/references/labels.md index 56b129e..63939f4 100644 --- a/plugins/gitea/skills/gitea-labels-milestones/references/labels.md +++ b/plugins/gitea/skills/gitea-labels-milestones/references/labels.md @@ -62,9 +62,15 @@ label_read method: "get_repo_label" owner: <owner> repo: <repo> id: <id> ## Resolve a name to an ID There is no direct name lookup. List all repo labels (paginating if needed), scan for a -case-insensitive name match, and extract `id`. This is the required first step before any label -application on an issue or PR — the actual `add_labels`/`replace_labels`/`remove_label` call lives -in `gitea-issues`/`gitea-prs` via `issue_write`/`pull_request_write`, which take numeric IDs only. +case-insensitive name match, and extract `id`. Both pools can apply to one issue: if the name is +not in `list_repo_labels`, also check `list_org_labels` before reporting it unresolved. That method +takes `org`, not `owner`/`repo` — pass the repo's `owner` as `org`, which is what it means when the +owner is an organisation. If that call fails, the owner is a user account, there is no org pool, and +the miss is a real miss. + +Resolution is the required first step before any label application on an issue or PR — the actual +`add_labels`/`replace_labels`/`remove_label` call lives in `gitea-issues`/`gitea-prs` via +`issue_write`/`pull_request_write`, which take numeric IDs only. ## Create a label diff --git a/plugins/gitea/skills/gitea-prs/README.md b/plugins/gitea/skills/gitea-prs/README.md index 7236390..4d07ed3 100644 --- a/plugins/gitea/skills/gitea-prs/README.md +++ b/plugins/gitea/skills/gitea-prs/README.md @@ -4,7 +4,7 @@ List, read, create, update, merge, and review Gitea pull requests. ## What it does -This skill handles the pull request lifecycle within the Gitea integration suite — listing and reading PRs (details, diff, changed files, CI status, reviews), creating them (title, body, labels), updating them (title, body, assignees, labels, milestone), managing reviewers, closing/reopening, merging with a chosen strategy and post-merge branch cleanup, and the full code-review flow (create a review with inline comments, submit it, dismiss or delete it). It composes `gitea-labels-milestones` for label/milestone ID resolution rather than duplicating that logic, and defers to `gitea-issues` for anything that turns out to be an issue rather than a PR (they share one number space) and to `gitea-branches`/`gitea-files` for the underlying branch/file operations behind a PR. +This skill handles the pull request lifecycle within the Gitea integration suite — listing and reading PRs (details, diff, changed files, CI status, reviews), creating them (title, body, labels), updating them (title, body, assignees, labels, milestone), adding and removing reviewers, closing/reopening, merging with a chosen strategy and post-merge branch cleanup, and the full code-review flow (create a review with inline comments, submit it, dismiss or delete it, reply to a review comment, and resolve or unresolve a comment thread). It composes `gitea-labels-milestones` for label/milestone ID resolution rather than duplicating that logic — `milestone` applies on an update only, never on create — and defers to `gitea-issues` for anything that turns out to be an issue rather than a PR (they share one number space) and to `gitea-branches`/`gitea-files` for the underlying branch/file operations behind a PR. ## Usage @@ -18,8 +18,8 @@ Describe the PR or review task: list PRs, get a PR's status/diff/reviews, create | File | Purpose | |------|---------| -| `SKILL.md` | Skill instructions for agents — Gotchas, composition with `gitea-labels-milestones`, and the dispatch table | +| `SKILL.md` | Skill instructions for agents — Gotchas, the dispatch table, and label/milestone ID resolution via `gitea-labels-milestones` | | `references/pull-requests.md` | Execution detail for `list_pull_requests`, `pull_request_read` (get/get_diff/get_files/get_status), and `pull_request_write` (create/update/close/reopen/update_branch/add_reviewers/remove_reviewers) | -| `references/reviews.md` | Execution detail for `pull_request_review_write` (create/submit/delete/dismiss) and the review-related `pull_request_read` methods | +| `references/reviews.md` | Execution detail for `pull_request_review_write` (create/submit/delete/dismiss, plus the comment-thread methods reply_comment/resolve_thread/unresolve_thread) and the review-related `pull_request_read` methods | | `references/merging.md` | The merge workflow — CI vs. review/branch-protection gates, merge styles, branch cleanup, and the post-merge issue-close check | | `references/sources.md` | Research sources backing the PR/review guidance | diff --git a/plugins/gitea/skills/gitea-prs/SKILL.md b/plugins/gitea/skills/gitea-prs/SKILL.md index c0c2545..b14dd3f 100644 --- a/plugins/gitea/skills/gitea-prs/SKILL.md +++ b/plugins/gitea/skills/gitea-prs/SKILL.md @@ -2,14 +2,8 @@ name: gitea-prs description: > - Use when listing, reading, creating, updating, merging, or reviewing Gitea pull requests — - getting PR status/diff/changed files/CI status, opening a PR, updating title/body/reviewers, - closing/reopening, merging with a chosen strategy, or submitting/dismissing a code review with - inline comments. Composes `gitea-labels-milestones` to resolve label names or milestone titles - to the numeric IDs `pull_request_write` requires, rather than duplicating that resolution logic. - Do not use for issues (`gitea-issues`) or branch/commit operations (`gitea-branches`) — a number - the user mentions may refer to either an issue or a PR since they share one number space, so - confirm which domain applies before dispatching. + Use when listing, reading, creating, updating, merging, or reviewing Gitea pull requests — even + when the user does not say "Gitea". Not issues -> `gitea-issues`. compatibility: Requires Gitea MCP server configured with write:issue and write:repository token scopes. @@ -20,49 +14,34 @@ metadata: - gitea-mcp-slim-go - context7-websites-gitea - context7-gitea-tea-cli - version: "0.1.1" + version: "0.1.2" allowed-tools: mcp__gitea__list_pull_requests mcp__gitea__pull_request_read mcp__gitea__pull_request_write mcp__gitea__pull_request_review_write --- ## Gotchas -- **Issues and PRs share one number space.** A number the user mentions (`#42`) might be an issue, not a PR — there is only one counter per repo. If you're not certain, call `pull_request_read method: "get"` and treat a 404 as "this number is an issue, not a PR" (or check `is_pull` on an `issue_read` response first if you already have one). -- **`pull_request_read method: "get"` returns `review_scomments`, not `review_comments`.** Source-level typo in gitea-mcp v1.3.0. Never reference `review_comments` — it will always be undefined. -- **`draft: true` on create prepends `"WIP:"` to the title.** Gitea has no first-class draft field — it implements draft PRs via title prefix. To un-draft, call `update` and pass the title without the `WIP:` prefix. -- **Cross-repo fork PRs require `head` as `"fork-owner:branch-name"`.** A bare branch name causes Gitea to search the base repo for it and return 422. Same-repo PRs use a bare branch name. -- **PR `milestone` is a bare title string, not `{id, title}`.** Unlike issues, you cannot recover a milestone's ID from a PR response. If you need the ID (e.g. to filter or to pass to another write), call into `gitea-labels-milestones` and match by title via `milestone_read method: "list"`. -- **CI status and review/approval state are independent merge gates.** `get_status` only reports CI. Branch-protection rules (required approvals, requested-reviewer coverage, stale-approval handling) are enforced server-side by the merge call itself and will error if unmet — passing CI does not mean the merge will succeed. -- **Reviews move through a state machine, not a single write.** `create` opens a review in `PENDING` state with inline comments attached; `submit` finalizes it with a terminal `state` (`APPROVED`/`REQUEST_CHANGES`/`COMMENT`). A submitted review can be `dismiss`ed afterward, but never deleted — `delete` only removes a review that was never submitted. -- **Merging a PR does not auto-close linked issues.** Unlike GitHub, Gitea has no merge-triggers-close event. It does parse closing keywords (`Fixes #N`, `Closes #N`) in commit messages landing on the default branch, so a non-squash merge that preserves those commit messages may auto-close the issue — but a squash merge rewrites history into one commit, so survival of the keyword depends on the squash commit's message. Always call `issue_read method: "get"` on any referenced issue after merging to check whether it already closed before deciding to close it explicitly. - -## Composing `gitea-labels-milestones` - -Before any `pull_request_write` call that includes a `labels` or `milestone` parameter, resolve names/titles to numeric IDs via `gitea-labels-milestones` — `label_read method: "list_repo_labels"` for label name → ID, `milestone_read method: "list"` for milestone title → ID. Never pass a label name string or milestone title string directly to `pull_request_write`; both parameters take numeric IDs only. This skill does not duplicate that lookup logic — it composes the shared skill. +- **Issues and PRs share one number space.** `#42` may be an issue rather than a PR. When unsure, call `pull_request_read method: "get"` and read a 404 as "that number is an issue" — hand it to `gitea-issues`. +- **`pull_request_write method: "create"` discards most optional parameters in silence.** `milestone`, `assignee`, `assignees`, `reviewers` and `team_reviewers` are accepted, dropped, and left out of the response, so a drop is indistinguishable from never passing them. `labels` *does* apply on `"create"`, so labels landing is no evidence the milestone did. ## Dispatch -| Task | Tool | method | -|---|---|---| -| List PRs | `list_pull_requests` | — | -| Get PR details | `pull_request_read` | `"get"` | -| Get PR diff | `pull_request_read` | `"get_diff"` | -| Get PR changed files | `pull_request_read` | `"get_files"` | -| Get PR CI status | `pull_request_read` | `"get_status"` | -| Get PR reviews | `pull_request_read` | `"get_reviews"` | -| Get one review | `pull_request_read` | `"get_review"` | -| Get review inline comments | `pull_request_read` | `"get_review_comments"` | -| Create a PR | `pull_request_write` | `"create"` | -| Update a PR | `pull_request_write` | `"update"` | -| Close a PR | `pull_request_write` | `"close"` | -| Reopen a PR | `pull_request_write` | `"reopen"` | -| Merge a PR | `pull_request_write` | `"merge"` | -| Update branch from base | `pull_request_write` | `"update_branch"` | -| Add reviewers | `pull_request_write` | `"add_reviewers"` | -| Remove reviewers | `pull_request_write` | `"remove_reviewers"` | -| Create a review | `pull_request_review_write` | `"create"` | -| Submit a review | `pull_request_review_write` | `"submit"` | -| Delete a review | `pull_request_review_write` | `"delete"` | -| Dismiss a review | `pull_request_review_write` | `"dismiss"` | +Resolve `owner` and `repo` from context first, and confirm the number names a PR before writing to it. -For full parameter detail on listing/reading/creating/updating/closing PRs, read `references/pull-requests.md`. For review-specific detail (create/submit/delete/dismiss, inline comment shape), read `references/reviews.md`. For the merge workflow specifically (CI gate, merge styles, branch cleanup, post-merge issue check), read `references/merging.md`. +| Task | Tool | Reference | +|---|---|---| +| List PRs; read a PR's details, diff, changed files or CI status | `list_pull_requests`, `pull_request_read` | `references/pull-requests.md` | +| Create a PR — subject to the silent-drop Gotcha above | `pull_request_write` | `references/pull-requests.md` | +| Update, close, reopen or retarget a PR, sync it with its base, or add/remove reviewers | `pull_request_write` | `references/pull-requests.md` | +| Merge a PR, or judge whether it can merge | `pull_request_write method: "merge"` | `references/merging.md` | +| Read, create, submit, dismiss or delete a code review, or reply to and resolve a review comment thread | `pull_request_read`, `pull_request_review_write` | `references/reviews.md` | + +Whatever `"create"` dropped takes a second call once the PR exists — `"update"` for milestone and assignees, `"add_reviewers"` for reviewers. + +Read the reference for the row you land on before making the call. Each carries the parameter signatures, the per-method behaviour and the response-shape quirks the row cannot, and every write method has at least one parameter that behaves differently from its issue-side counterpart. + +## Resolving labels and milestones + +`labels` and `milestone` take numeric IDs, never name or title strings. Before a `pull_request_write` call carrying either, resolve them through `gitea-labels-milestones`: `label_read method: "list_repo_labels"` for a label name, `milestone_read method: "list"` for a milestone title. + +Resolve a milestone only when the call is an `"update"` — on `"create"` the lookup is wasted, per the Gotcha above. Recovering an existing PR's milestone ID needs the same lookup, because `pull_request_read` returns `milestone` as a bare title string and never an ID. diff --git a/plugins/gitea/skills/gitea-prs/references/pull-requests.md b/plugins/gitea/skills/gitea-prs/references/pull-requests.md index 05084d4..7f92032 100644 --- a/plugins/gitea/skills/gitea-prs/references/pull-requests.md +++ b/plugins/gitea/skills/gitea-prs/references/pull-requests.md @@ -7,7 +7,7 @@ source_keys: # Pull request read/write execution detail -Parameter signatures below are cross-checked live against the deployed gitea-mcp server tool schemas at authoring time — not copied verbatim from the plugin's research doc for this domain, which has a known history of drifting from the deployed server (e.g. a prior `type` parameter that no longer exists on `list_issues`, and the `review_scomments` typo covered in `references/reviews.md`). Re-verify via `ToolSearch` before trusting this file if the gitea-mcp version changes. +Parameter signatures below are cross-checked live against the deployed gitea-mcp server tool schemas at authoring time — not copied verbatim from the plugin's research doc for this domain, which has a known history of drifting from the deployed server (e.g. a prior `type` parameter that no longer exists on `list_issues`). These files were last verified against gitea-mcp **v1.7.0**, as reported by `get_gitea_mcp_server_version`. Re-verify via `ToolSearch` before trusting this file if the deployed version differs — drift has bitten this skill in both directions, adding methods it does not list and fixing quirks it still warns about. ## `list_pull_requests` @@ -29,14 +29,14 @@ List responses trim PRs down to summary fields — `head`/`base` are bare ref st - `owner` (string, required) - `repo` (string, required) - `pull_number` (number, required) -- `review_id` (number, optional) — required for `"get_review"` and `"get_review_comments"`; see `references/reviews.md` +- `review_id` (number, optional) — required for `"get_review"`, which errors with `review_id is required` without it. **Optional** for `"get_review_comments"`: omit it to list every inline comment on the PR in one call. See `references/reviews.md` - `binary` (boolean, optional) — include binary diff content for `"get_diff"` - `page` (number, optional, default 1) - `per_page` (number, optional, default 30) `"get"`, `"get_diff"`, `"get_files"`, and `"get_status"` are covered here. `"get_reviews"`, `"get_review"`, and `"get_review_comments"` are covered in `references/reviews.md`. -- `"get"` returns the full PR object: state, draft, merged, mergeable flags; `head`/`base` as full objects (`{ref, sha, repo?}`); `milestone` as a bare title string (not `{id, title}`); `review_scomments` (typo, see `references/reviews.md`). +- `"get"` returns the full PR object: state, draft, merged, mergeable flags; `head`/`base` as full objects (`{ref, sha, repo?}`); `milestone` as a bare title string (not `{id, title}`); and `review_comments` as an integer count, not comment objects (see `references/reviews.md`). - `"get_diff"` returns raw diff text. - `"get_files"` returns the list of changed file objects. - `"get_status"` returns the combined commit status for the PR's head commit — CI result only, not review/approval state (see `references/merging.md`). diff --git a/plugins/gitea/skills/gitea-prs/references/reviews.md b/plugins/gitea/skills/gitea-prs/references/reviews.md index e21d23d..77003b2 100644 --- a/plugins/gitea/skills/gitea-prs/references/reviews.md +++ b/plugins/gitea/skills/gitea-prs/references/reviews.md @@ -7,7 +7,7 @@ source_keys: # PR review execution detail -Parameter signatures below are cross-checked live against the deployed gitea-mcp server tool schema, not copied from the plugin's research doc verbatim — same sourcing discipline as `references/pull-requests.md`. +Parameter signatures below are cross-checked live against the deployed gitea-mcp server tool schema, not copied from the plugin's research doc verbatim — same sourcing discipline as `references/pull-requests.md`. Last verified against **v1.7.0**, as reported by `get_gitea_mcp_server_version`. ## Review state machine @@ -21,23 +21,34 @@ A review is not a single write. It moves through states: ## `pull_request_review_write` **Parameters:** -- `method` (string, required) — `"create"` | `"submit"` | `"delete"` | `"dismiss"` +- `method` (string, required) — `"create"` | `"submit"` | `"delete"` | `"dismiss"` | `"reply_comment"` | `"resolve_thread"` | `"unresolve_thread"` - `owner` (string, required) - `repo` (string, required) -- `pull_number` (number, required) -- `review_id` (number, required for every method except `"create"`, which returns the ID to use for the follow-up `submit`/`delete`/`dismiss` call) +- `pull_number` (number, required for every method except `"resolve_thread"` and `"unresolve_thread"`, which locate the thread from `comment_id` alone) — the schema's own `required` list is only `method`/`owner`/`repo`, so a missing `pull_number` surfaces as a runtime error, not client-side validation +- `review_id` (number) — required for `"submit"`, `"delete"` and `"dismiss"`; `"create"` returns the ID to use for that follow-up call. Not used by `"reply_comment"`, `"resolve_thread"` or `"unresolve_thread"`, which key off `comment_id` instead +- `comment_id` (number, required for `"reply_comment"`, `"resolve_thread"` and `"unresolve_thread"`) — an individual review comment's ID, obtained from `pull_request_read method: "get_review_comments"`. For the two thread methods this must be the thread's **first** comment, not an arbitrary one in it - `state` (string, optional) — `"APPROVED"` | `"REQUEST_CHANGES"` | `"COMMENT"` | `"PENDING"` — set on `"create"` (typically `"PENDING"`, or a terminal state to create-and-submit in one call if the server supports it) or `"submit"` (terminal state) -- `body` (string, optional) — overall review comment text +- `body` (string, optional) — the overall review comment text on `"create"`/`"submit"`; on `"reply_comment"` it is the reply text and is the payload of the call - `commit_id` (string, optional, for `"create"`) — anchors inline comments to a specific commit SHA (typically the PR's current head SHA from `pull_request_read method: "get"`) - `message` (string, optional, for `"dismiss"`) — dismissal reason - `comments` (array of objects, optional, for `"create"`) — inline comments, each: `{path, body, old_line_num, new_line_num}` — `path` is the file path, `body` is the comment text, `new_line_num` anchors to a line in the new (added) side of the diff, `old_line_num` anchors to a line in the old (removed) side; use whichever side the comment applies to, not both +## Comment threads + +Three further methods act on an individual review comment rather than on a review as a whole. They sit outside the state machine above — a thread can be replied to or resolved whatever state its parent review is in — and none of them takes a `review_id`. + +- **`reply_comment`** — posts `body` as a reply to the comment named by `comment_id`, threading under it rather than starting a new top-level comment. Takes `pull_number`. +- **`resolve_thread`** — marks the thread containing `comment_id` resolved. Pass the thread's **first** comment ID; another ID in the same thread is not equivalent. Does not take `pull_number`. +- **`unresolve_thread`** — reopens a resolved thread, under the same first-comment rule. + +Get the `comment_id` from `pull_request_read method: "get_review_comments"`. Call it with no `review_id` to list every inline comment on the PR, then pick the thread to act on; scoping it to one `review_id` only finds threads opened by that review. + ## Reading reviews (`pull_request_read`) - `method: "get_reviews"` — array of review summaries: `id`, `state`, `body`, `user` (login), `comments_count`, `submitted_at`, `html_url`, `stale` (bool — the PR was pushed to after this review was submitted, meaning it may be outdated), `official` (bool), `dismissed` (bool). -- `method: "get_review"` (requires `review_id`) — single review detail. -- `method: "get_review_comments"` (requires `review_id`) — array of inline comments: `id`, `body`, `path`, `position`, `old_position`, `diff_hunk`, `user`, `html_url`, `created_at`, `updated_at`. +- `method: "get_review"` (requires `review_id` — omitting it fails with `review_id is required`) — single review detail. +- `method: "get_review_comments"` (`review_id` **optional** — omit it to list every inline comment on the PR in one call, rather than one review's) — array of inline comments: `id`, `body`, `path`, `position`, `old_position`, `diff_hunk`, `user`, `html_url`, `created_at`, `updated_at`. -**`review_scomments` typo:** the full PR object returned by `pull_request_read method: "get"` includes a field named `review_scomments` (a count), not `review_comments` — a source-level misspelling in gitea-mcp v1.3.0's `slim.go`. Do not write code or instructions that reference `review_comments` on that response; it will always be `undefined`. This is distinct from the `get_review_comments` method above, which is spelled correctly and returns the actual comment objects. +**`review_comments` on the `"get"` response is a count, not the comments.** The full PR object returned by `pull_request_read method: "get"` carries `review_comments` as an integer — the number of inline review comments. It is distinct from the `get_review_comments` method above, which returns the actual comment objects; reading the count is no substitute for that call. Older gitea-mcp releases misspelled this key as `review_scomments`; the misspelling was corrected upstream and the deployed v1.7.0 response carries no such key, so treat any instruction that reaches for `review_scomments` as stale. **Inline-comment field names differ between write and read.** The `comments` array on `pull_request_review_write method: "create"` uses `old_line_num`/`new_line_num`. The `get_review_comments` read response uses different field names for the same concept — `position` (new-side line) and `old_position` (old-side line). Do not assume the same key names apply on both sides of the round trip. diff --git a/plugins/gitea/skills/gitea-prs/references/sources.md b/plugins/gitea/skills/gitea-prs/references/sources.md index 90d86a6..bc464f6 100644 --- a/plugins/gitea/skills/gitea-prs/references/sources.md +++ b/plugins/gitea/skills/gitea-prs/references/sources.md @@ -3,7 +3,7 @@ ## gitea-mcp-repo - **URL:** https://gitea.com/gitea/gitea-mcp -- **Description:** Official gitea-mcp repository (v1.3.0) — `operation/*.go` source files documenting all 55 MCP tools, their parameters, and CLI flags. Live tool schemas (`list_pull_requests`, `pull_request_read`, `pull_request_write`, `pull_request_review_write`) were verified directly against the deployed MCP server via `ToolSearch` at authoring time, per this repo's process for resolving schema-vs-docs drift, rather than copied from the derived research doc. +- **Description:** Official gitea-mcp repository — `operation/*.go` source files documenting the MCP tools, their parameters, and CLI flags. Live tool schemas (`list_pull_requests`, `pull_request_read`, `pull_request_write`, `pull_request_review_write`) are verified directly against the deployed MCP server via `ToolSearch`, per this repo's process for resolving schema-vs-docs drift, rather than copied from the derived research doc. **Last verified against v1.7.0**, as reported by `get_gitea_mcp_server_version`; the files were originally authored against v1.3.0 and the read/review side had drifted by three defects before that re-verification. - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md - **Contributing files:** SKILL.md, references/pull-requests.md, references/reviews.md, references/merging.md - **Status:** `extracted` @@ -11,7 +11,7 @@ ## gitea-mcp-slim-go - **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/pull/slim.go -- **Description:** Slim response shape structs from gitea-mcp source — defines exactly which fields the MCP server returns for PRs and reviews, including the `review_scomments` typo and the PR-response milestone-as-title-string quirk. +- **Description:** Slim response shape structs from gitea-mcp source — defines exactly which fields the MCP server returns for PRs and reviews, including the PR-response milestone-as-title-string quirk. The `review_scomments` misspelling this file documented at v1.3.0 was corrected upstream; v1.7.0 returns `review_comments`. - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md - **Contributing files:** SKILL.md, references/pull-requests.md, references/reviews.md - **Status:** `extracted` @@ -21,7 +21,7 @@ - **URL:** context7:/websites/gitea - **Description:** Official Gitea docs mirror on Context7 — branch protection rules, PR review/merge gating behavior, and automatic issue/PR cross-reference linking. Backfills the external/best-practice gap left by the original docs.gitea.com fetch timeout. - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md -- **Contributing files:** SKILL.md, references/merging.md +- **Contributing files:** references/merging.md - **Status:** `extracted` ## context7-gitea-tea-cli diff --git a/plugins/gitea/skills/gitea-releases/SKILL.md b/plugins/gitea/skills/gitea-releases/SKILL.md index 7bcc8e9..a573c4c 100644 --- a/plugins/gitea/skills/gitea-releases/SKILL.md +++ b/plugins/gitea/skills/gitea-releases/SKILL.md @@ -2,12 +2,9 @@ name: gitea-releases description: > - Use when managing Gitea releases and tags for a repository: listing, creating, or deleting - releases (with draft/prerelease flags and release notes), and listing, creating, or deleting the - underlying git tags. Use even if the user doesn't say "release" explicitly — "cut a v1.2.0", - "publish a prerelease", "tag this commit", or "what's the latest release" all apply. Do not use - for git branch or commit history operations (use gitea-branches) or for issue/PR management (use - gitea-issues / gitea-prs). + Use when managing Gitea releases or the git tags underneath them — list, get, + create, or delete either — even when the user does not say "release" or + "Gitea". Not branches or commit history -> `gitea-branches`. metadata: category: gitea @@ -20,11 +17,9 @@ metadata: ## Gotchas -- **`delete_release` takes a numeric `id`, never a tag name.** `delete_tag` is the mirror opposite — it takes the `tag_name` string, never a numeric id. These two tools are asymmetric on purpose; passing a tag name to `delete_release` or a numeric id to `delete_tag` fails. Always resolve the numeric release id via `list_releases` or `get_release` first if you only have a tag name in hand. -- **Deleting a release does not delete its tag.** They are separate destructive operations against separate resources — a release is a wrapper (title, notes, draft/prerelease flags, assets) around a tag, not the tag itself. If the intent is to remove both, call `delete_release` and `delete_tag` separately. -- **`list_releases`/`list_tags` default to `per_page: 20`**, unlike most other gitea-mcp tools which default to 30. The MCP layer does no auto-pagination — to get a complete result set, loop `page` upward until a page returns fewer than `per_page` results. -- **`is_draft`/`is_pre_release` are explicit booleans the caller sets on `create_release` — never inferred from `tag_name`.** Note the input param is `is_draft`, which maps to the `draft` field on the *response* object (see Dispatch table below and `references/call-signatures.md`) — `draft` is never a valid input key. Practitioner convention (per the `tea` CLI) uses `-beta`/`-rc` suffixes for prereleases (e.g. `v2.0.0-beta.1`), but Gitea does not enforce or infer this from the tag string. If the user names a tag that looks like a prerelease, set `is_pre_release: true` explicitly rather than assuming the flag is redundant with the name. -- **Tag names are conventionally semver, `v`-prefixed** (`v1.2.0`, `v2.0.0-beta.1`), but this is a practitioner convention, not a Gitea constraint — don't reject or rewrite a caller-supplied tag name that doesn't follow it. +- **Deleting a release never deletes its tag, and deleting a tag never deletes the release wrapping it.** A release is a metadata wrapper around a tag, so removing both takes two independent destructive calls. +- **`is_draft`/`is_pre_release` are booleans the caller sets — Gitea never infers a prerelease from a `-beta`/`-rc` tag name.** The response object names them `draft`/`prerelease`; passing `draft` as an input key is silently ignored, not rejected. +- **`list_releases`/`list_tags` default `per_page` to 20**, where most other gitea-mcp list tools default to 30 — a caller assuming 30 under-counts the pages a full sweep needs. ## Dispatch table @@ -40,13 +35,13 @@ metadata: | Create tag | `create_tag` | `owner`, `repo`, `tag_name` | `target`, `message` | | Delete tag | `delete_tag` | `owner`, `repo`, `tag_name` | — | -`target` (on `create_release`/`create_tag`) is a commitish — a branch name, existing tag, or commit SHA — the point the new tag is cut from. See `references/call-signatures.md` for response shapes. +`target` (on `create_release`/`create_tag`) is a commitish — a branch name, existing tag, or commit SHA — the point the new tag is cut from. ## Workflow -- [ ] **Creating a release:** Call `create_release` directly with `tag_name` + `target` + `title` — Gitea is assumed to create the underlying tag automatically if `tag_name` doesn't already exist (this is plausible behavior inferred from the API shape, not directly confirmed in the research docs), so a separate `create_tag` call is only needed when you want to tag a commit without wrapping it in a release yet. Verify the tag exists afterward if this matters to the caller. Set `is_pre_release`/`is_draft` explicitly per the Gotchas above; don't leave them to default inference. -- [ ] **Deleting a release safely:** Resolve the numeric id first — call `list_releases` (paginate if needed, see Gotchas) or `get_release` if the id is already known, find the entry matching the target `tag_name`, then call `delete_release` with that `id`. Never pass `tag_name` to `delete_release`. -- [ ] **Deleting a tag along with its release:** Delete the release first (frees the id lookup), then call `delete_tag` with the `tag_name` separately — confirm both are intended before proceeding, since each is an independent irreversible operation. -- [ ] **Listing every page:** If the caller needs all releases or tags (not just the first page), loop `page: 1, 2, 3...` until a response has fewer than `per_page` entries. +- [ ] **Creating a release:** Call `create_release` with `tag_name`, `target`, and `title`. Gitea is assumed to create the tag from `target` when `tag_name` does not yet exist — plausible from the API shape, not confirmed in the research docs — so a separate `create_tag` is only needed to tag a commit without wrapping it in a release. Verify with `get_tag` afterward if the caller depends on it. +- [ ] **Deleting a release:** `delete_release` takes the numeric `id` and never a `tag_name`; `delete_tag` is the mirror opposite and never takes an id. With only a tag name in hand, resolve the id through `list_releases` (paginating if needed) or `get_release` first. +- [ ] **Deleting a tag along with its release:** Delete the release first, then call `delete_tag` — confirm both are intended before proceeding, since each is irreversible on its own. +- [ ] **Listing every page:** Loop `page: 1, 2, 3...` until a response returns fewer than `per_page` entries. Nothing here auto-paginates. -If exact response field shapes or additional conventions are needed, read `references/call-signatures.md` and `references/conventions.md`. +If exact input params or response field shapes are needed, read `references/call-signatures.md`. If the caller raises semver tag naming, release-notes sourcing, or how a release relates to its tag, read `references/conventions.md`. diff --git a/plugins/gitea/skills/gitea-releases/references/sources.md b/plugins/gitea/skills/gitea-releases/references/sources.md index 2b67a9f..cffb8b9 100644 --- a/plugins/gitea/skills/gitea-releases/references/sources.md +++ b/plugins/gitea/skills/gitea-releases/references/sources.md @@ -42,7 +42,6 @@ - **Research doc:** plugins/gitea/docs/research/docs/gitea/workflow-conventions.md (Release and tag conventions section) **Contributing files:** -- SKILL.md (Gotchas — semver tag naming) -- references/conventions.md +- references/conventions.md (semver tag naming, release-notes sourcing) **Status:** `extracted` diff --git a/plugins/gitea/skills/gitea-workflow/README.md b/plugins/gitea/skills/gitea-workflow/README.md index 4c81d20..02ebd19 100644 --- a/plugins/gitea/skills/gitea-workflow/README.md +++ b/plugins/gitea/skills/gitea-workflow/README.md @@ -4,7 +4,7 @@ Human-facing entry point and router for the Gitea integration. ## What it does -This skill is the conversational front door to the Gitea suite — it replaces the old flat `/gitea` skill. On its own it never calls a Gitea MCP tool; it composes the six domain skills (`gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, `gitea-releases`). It handles the no-args status check-in (open issues + open PRs), resolves ambiguous issue-or-PR numbers before dispatching (issues and PRs share one number space), and points a user or agent to the right domain skill when it's unclear which one applies. +This skill is the conversational front door to the Gitea suite — it replaces the old flat `/gitea` skill. On its own it never calls a Gitea MCP tool; it composes the six domain skills (`gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, `gitea-releases`). It handles the no-args status check-in (open issues + open PRs), which preserves the original flat `/gitea` skill's default behavior; resolves ambiguous issue-or-PR numbers before dispatching (issues and PRs share one number space); and points a user or agent to the right domain skill when it's unclear which one applies. ## Usage @@ -18,5 +18,8 @@ Invoke with no arguments for a status check-in, with a bare number to resolve an | File | Purpose | |------|---------| -| `SKILL.md` | Skill instructions for agents — Gotchas, status view, ambiguous-number resolution, and the domain-skill index | +| `SKILL.md` | Skill instructions for agents — Gotchas, the dispatch table keyed on invocation shape, and the common report gate every branch ends in — each branch's own format lives with its reference file | +| `references/status-checkin.md` | Loaded when the skill is invoked with no specific request — the two parallel open-issue/open-PR reads and the two-section report | +| `references/number-resolution.md` | Loaded when the request carries a bare number that says neither "issue" nor "PR" — the `is_pull` resolution call and the hidden-permission-error 404 | +| `references/skill-index.md` | Loaded when the request names a capability but not which skill owns it — the six-skill routing index | | `references/sources.md` | Research sources backing the routing/status guidance | diff --git a/plugins/gitea/skills/gitea-workflow/SKILL.md b/plugins/gitea/skills/gitea-workflow/SKILL.md index 92b7a6e..eeee87b 100644 --- a/plugins/gitea/skills/gitea-workflow/SKILL.md +++ b/plugins/gitea/skills/gitea-workflow/SKILL.md @@ -2,25 +2,17 @@ name: gitea-workflow description: > - Use when a human wants a general or ambiguous Gitea status check or isn't sure which Gitea - domain skill applies — a no-args check-in ("what's going on in the repo", "any updates?"), - a bare-numbered reference that could be an issue or a PR ("what's the status of #42", "what's - happening with #17"), or a request to discover which Gitea capability handles a task. This is - the human-facing entry point and router for the Gitea integration — it replaces the old flat - `/gitea` invocation (now `/gitea-workflow`) and composes the six domain skills - (`gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, - `gitea-releases`) rather than calling any Gitea MCP tool directly. Do not use this skill when - the domain is already known and unambiguous — invoke the matching domain skill directly instead - (e.g. "create an issue" → `gitea-issues`, "merge PR #10" → `gitea-prs`, "cut a release" → - `gitea-releases`). Do not use for local git operations with no Gitea component (use - `git-workflow`). + Use when a Gitea request is general or ambiguous — a no-args repo check-in, a bare number that + could be an issue or a PR, or a capability whose owning skill is unclear. Resolves which + domain skill applies. Not an unambiguous issue request -> `gitea-issues`. Not an + unambiguous PR request -> `gitea-prs`. Not local git work with no Gitea component. compatibility: Requires Gitea MCP server configured with a token; delegates all calls to the six domain skills, which in turn require write:issue and write:repository scopes at minimum. metadata: category: integration - version: "0.1.0" + version: "0.1.3" source_keys: - gitea-mcp-repo - gitea-mcp-slim-go @@ -29,46 +21,24 @@ metadata: ## Gotchas -- **This skill never calls a Gitea MCP tool itself.** Every read or write goes through one of the six domain skills. If a request needs a raw `mcp__gitea__*` call that no domain skill exposes, that's a gap in a domain skill, not something to patch here. -- **Issues and PRs share one number space** — a bare number like `#42` could be either. Never guess from context clues alone; resolve it with a real call (see Step 2) before dispatching. -- **A 404 on the resolution call doesn't necessarily mean the number doesn't exist.** Gitea hides permission errors as not-found (documented in `gitea-issues`' Gotchas). If resolution 404s unexpectedly, say so and suggest checking token scope rather than reporting "no such issue or PR." +- **This skill never calls a Gitea MCP tool itself.** Every read and write goes through a domain skill. A needed `mcp__gitea__*` call that no domain skill exposes is a gap in that skill, not something to patch here. -## Step 1 — Default status view (no args) +## Dispatch -When invoked with no specific request, give a status check-in: +The invocation's shape selects exactly one branch. -1. Invoke `gitea-issues` to list open issues (`state: "open"`). -2. Invoke `gitea-prs` to list open PRs (`state: "open"`). -3. Run both in parallel — they're independent reads. -4. Report as two sections, "Open Issues" and "Open Pull Requests", each as a compact list (number, title). This preserves the original flat `/gitea` skill's default behavior. +| Invocation shape | Flow | Reference | +|---|---|---| +| No arguments, no specific request | Repo status check-in | `references/status-checkin.md` | +| A bare number, with neither "issue" nor "PR" said | Resolve which domain the number belongs to | `references/number-resolution.md` | +| A named capability whose owning skill is unclear | Route to the domain skill that owns it | `references/skill-index.md` | -## Step 2 — Resolve an ambiguous number +If the invocation carries no specific request, read `references/status-checkin.md`. -When the user references a bare number without saying "issue" or "PR" (e.g. "what's going on with #42"): +If the request references a bare number and never says "issue" or "PR", read `references/number-resolution.md`. -1. Invoke `gitea-issues` to run `issue_read method: "get"` on that number. -2. Check the response's `is_pull` field: - - `true` → it's a PR. Invoke `gitea-prs` for full PR detail (status, diff, reviews as appropriate to the request) and present that instead. - - `false` or absent → it's an issue. Present the issue detail already retrieved. -3. If the resolution call 404s, don't conclude the number doesn't exist — report the 404 and suggest verifying token scope (`write:issue`) per `gitea-issues`' Gotchas, since permission errors are hidden as not-found in Gitea. +If the request names a capability but not which skill owns it, read `references/skill-index.md`. -Never dispatch to `gitea-issues` or `gitea-prs` based on guessing from phrasing alone ("that sounds like a bug" is not evidence) — always resolve first. +## Report -## Step 3 — Route explicit but domain-unclear requests - -For requests that name a capability but not obviously which skill owns it, use this index: - -| Skill | Covers | -|---|---| -| `gitea-issues` | List/read/create/update issues, comments, search across issues and PRs. Composes `gitea-labels-milestones` for label/milestone resolution. | -| `gitea-labels-milestones` | Label and milestone CRUD, label inference from conversation context, resolving names/titles to the numeric IDs writes require. Cross-cutting — used by both `gitea-issues` and `gitea-prs`. | -| `gitea-prs` | List/read/create/update/merge PRs, code reviews. Composes `gitea-labels-milestones` the same way `gitea-issues` does. | -| `gitea-branches` | Branch list/create/delete, plus commit history (list commits, get a single commit by SHA). | -| `gitea-files` | Read/write/delete individual files, list a directory, walk the full repo tree. | -| `gitea-releases` | Release and tag CRUD — draft/prerelease flags, release notes, semver tags. | - -If a request clearly names one of these (e.g. "create a milestone" → `gitea-labels-milestones`, "read this file from the repo" → `gitea-files`), invoke that skill directly rather than routing through here. Use this table only when the user or an upstream agent is unsure which skill applies. - -## Step 4 — Report - -Present results in plain language. For the status view, two labeled sections. For a resolved ambiguous number, say which domain it turned out to be before showing detail ("That's a pull request:" / "That's an issue:"). For routing, name the skill and hand off — don't duplicate its output format, let it report. +Every branch ends here. Present results in plain language; the branch's reference file carries its format. diff --git a/plugins/gitea/skills/gitea-workflow/references/number-resolution.md b/plugins/gitea/skills/gitea-workflow/references/number-resolution.md new file mode 100644 index 0000000..280d567 --- /dev/null +++ b/plugins/gitea/skills/gitea-workflow/references/number-resolution.md @@ -0,0 +1,19 @@ +--- +topic: number-resolution +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go + - context7-websites-gitea +--- + +# Resolving a bare issue-or-PR number + +The user has referenced a bare number without saying "issue" or "PR" (e.g. "what's going on with #42"). Resolve it with a real call — never dispatch to `gitea-issues` or `gitea-prs` by guessing from phrasing alone, because "that sounds like a bug" is not evidence and the two domains share one number space. + +1. Invoke `gitea-issues` to run `issue_read method: "get"` on that number. +2. Check the response's `is_pull` field: + - `true` → it's a PR. Invoke `gitea-prs` for full PR detail (status, diff, reviews as appropriate to the request) and present that instead. + - `false` or absent → it's an issue. Present the issue detail already retrieved. +3. If the resolution call 404s, don't conclude the number doesn't exist. Gitea hides permission errors as not-found (documented in `gitea-issues`' Gotchas), so report the 404 and suggest verifying the token carries `write:issue` rather than reporting "no such issue or PR." + +Then report per `SKILL.md`'s Report section, saying which domain the number turned out to be before showing detail ("That's a pull request:" / "That's an issue:") — otherwise the user cannot tell the resolution happened. diff --git a/plugins/gitea/skills/gitea-workflow/references/skill-index.md b/plugins/gitea/skills/gitea-workflow/references/skill-index.md new file mode 100644 index 0000000..e556e75 --- /dev/null +++ b/plugins/gitea/skills/gitea-workflow/references/skill-index.md @@ -0,0 +1,22 @@ +--- +topic: skill-index +source_keys: + - gitea-mcp-repo +--- + +# Domain-skill index + +The request names a capability but not obviously which skill owns it. Find the owner here, then invoke it: + +| Skill | Covers | +|---|---| +| `gitea-issues` | List/read/create/update issues, comments, search across issues and PRs. Composes `gitea-labels-milestones` for label/milestone resolution. | +| `gitea-labels-milestones` | Label and milestone CRUD, label inference from conversation context, resolving names/titles to the numeric IDs writes require. Cross-cutting — used by both `gitea-issues` and `gitea-prs`. | +| `gitea-prs` | List/read/create/update/merge PRs, code reviews. Composes `gitea-labels-milestones` the same way `gitea-issues` does. | +| `gitea-branches` | Branch list/create/delete, plus commit history (list commits, get a single commit by SHA). | +| `gitea-files` | Read/write/delete individual files, list a directory, walk the full repo tree. | +| `gitea-releases` | Release and tag CRUD — draft/prerelease flags, release notes, semver tags. | + +A request that already names its own owner (e.g. "create a milestone" → `gitea-labels-milestones`, "read this file from the repo" → `gitea-files`) never needed this index — invoke that skill directly rather than routing through here. + +Then report per `SKILL.md`'s Report section: name the skill and hand off — don't duplicate its output format, let it report. diff --git a/plugins/gitea/skills/gitea-workflow/references/sources.md b/plugins/gitea/skills/gitea-workflow/references/sources.md index 852e7af..27d4fe7 100644 --- a/plugins/gitea/skills/gitea-workflow/references/sources.md +++ b/plugins/gitea/skills/gitea-workflow/references/sources.md @@ -3,9 +3,9 @@ ## gitea-mcp-repo - **URL:** https://gitea.com/gitea/gitea-mcp -- **Description:** Official gitea-mcp repository (v1.3.0) — `operation/*.go` source files documenting all 55 MCP tools. This skill's status view relies on `list_issues`/`list_pull_requests` semantics (via `gitea-issues`/`gitea-prs`), and its ambiguous-number resolution relies on `issue_read`'s `is_pull` field, both verified against this source at authoring time. +- **Description:** Official gitea-mcp repository (v1.3.0) — `operation/*.go` source files documenting all 55 MCP tools. This skill's status view relies on `list_issues`/`list_pull_requests` semantics (via `gitea-issues`/`gitea-prs`), its ambiguous-number resolution relies on `issue_read`'s `is_pull` field, and its domain-skill index groups that tool surface by owning skill — all verified against this source at authoring time. - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md -- **Contributing files:** SKILL.md +- **Contributing files:** references/status-checkin.md, references/number-resolution.md, references/skill-index.md - **Status:** `extracted` ## gitea-mcp-slim-go @@ -13,7 +13,7 @@ - **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/issue/slim.go - **Description:** Slim response shape structs from gitea-mcp source — confirms `is_pull` is present on a single-item `issue_read` response, the field this skill's resolution step depends on to distinguish an issue from a PR sharing the same number. - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md -- **Contributing files:** SKILL.md +- **Contributing files:** references/number-resolution.md - **Status:** `extracted` ## context7-websites-gitea @@ -21,7 +21,7 @@ - **URL:** context7:/websites/gitea - **Description:** Official Gitea docs mirror on Context7 — confirms issues and pull requests share a single per-repository number sequence, and that Gitea returns 404 for permission failures rather than a distinct 403, both facts this skill's resolution and error-handling steps depend on. - **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md -- **Contributing files:** SKILL.md +- **Contributing files:** references/number-resolution.md - **Status:** `extracted` ## context7-gitea-tea-cli diff --git a/plugins/gitea/skills/gitea-workflow/references/status-checkin.md b/plugins/gitea/skills/gitea-workflow/references/status-checkin.md new file mode 100644 index 0000000..987ce6f --- /dev/null +++ b/plugins/gitea/skills/gitea-workflow/references/status-checkin.md @@ -0,0 +1,15 @@ +--- +topic: status-checkin +source_keys: + - gitea-mcp-repo +--- + +# Status check-in (invoked with no request) + +Give a repo status check-in: + +1. Invoke `gitea-issues` to list open issues (`state: "open"`). +2. Invoke `gitea-prs` to list open PRs (`state: "open"`). Run this alongside step 1 — the two are independent reads, so serialising them only adds latency. +3. Report as two sections, "Open Issues" and "Open Pull Requests", each a compact list of number and title. + +Then report per `SKILL.md`'s Report section.