fix(gitea): restore routing and sourcing content the retrofit dropped

gitea-issues asserted flatly that a merge never closes an issue, contradicting
gitea-prs' references/merging.md, which documents that closing keywords in
commits landing on the default branch do close one. The qualifier that made the
claim true had been deleted; both sides now agree.

gitea-releases had lost an epistemic hedge and its verification step, leaving
conventions.md asserting unconfirmed tag auto-creation as fact. Nothing in the
research corpus sources it, so the hedge and the verify-afterward instruction are
back rather than upgraded.

Descriptions were cut 50-240 chars under the 400 budget and shed routing with
them: gitea-workflow's boundary named no target, gitea-prs lost the issue/PR
number-space directive the suite is built around at 163/400, gitea-releases lost
its boundary and every trigger. Restored, inside budget. Also restores
delete_branch's hard-refusal strength and gitea-files' Read/Write/Edit pointer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EJJrm5YmacbwMdzZpXcoti
This commit is contained in:
2026-08-31 19:46:32 +00:00
parent a8cd5e881d
commit 1a971ee003
16 changed files with 78 additions and 46 deletions

View File

@@ -24,7 +24,7 @@ allowed-tools: Bash mcp__gitea__list_branches mcp__gitea__create_branch mcp__git
- **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`.
- **`delete_branch` has no force-push guard.** Treat deleting a protected branch as a hard refusal unless the user explicitly confirms it in the conversation. Check `protected` from `list_branches` first — a protected branch need not be named `main`.
## Step 1 — Resolve owner and repo

View File

@@ -3,8 +3,8 @@ name: gitea-files
description: >
Use when reading or writing files or directories in a Gitea repository via the MCP server,
rather than the local filesystem — even when the user does not say "Gitea". Not commit
history -> `gitea-branches`. Not pull requests -> `gitea-prs`.
rather than the local filesystem (for a local path use Read/Write/Edit) — 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

View File

@@ -2,8 +2,9 @@
name: gitea-issues
description: >
Use when reading or writing Gitea issues — even when the user does not say "Gitea".
Not pull requests -> `gitea-prs`.
Use when reading or writing Gitea issues — "create an issue", "what issues are open",
"close issue #N", "comment on issue #N", "search issues for X" — 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
@@ -25,14 +26,14 @@ allowed-tools: Bash mcp__gitea__list_issues mcp__gitea__issue_read mcp__gitea__i
## Gotchas
- **`list_issues` mixes in PRs unless you filter.** Issues and PRs share one repo number space; pass `type: "issues"` to exclude PRs (or `"pulls"` for only PRs). Nothing on a list item flags which is which — `is_pull` appears only on `issue_read method: "get"`, never on a list item.
- **`list_issues` mixes in PRs unless you filter.** Issues and PRs share one repo number space; pass `type: "issues"` to exclude PRs (or `"pulls"` for only PRs). Nothing on a list item flags which is which — `is_pull` appears only on `issue_read method: "get"`.
- **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 merge does not itself close the issue.** Gitea has no close-on-merge event, but a `Fixes #N` in the merged commits can, depending on merge style (`gitea-prs`). Re-read its 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
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:
An orchestrating caller may pass `owner` and `repo` in already, and the `search` row is cross-repository and needs only a query — both skip this step. Otherwise, before any tool call:
```bash
git remote get-url origin
@@ -58,7 +59,7 @@ One invocation takes one row. Read only the reference(s) that row names — the
## Step 3 — Create
Only the create flow reaches this step; every other row goes straight to its reference.
Only the create flow reaches this step.
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.

View File

@@ -2,8 +2,9 @@
name: gitea-labels-milestones
description: >
Use when reading or writing Gitea labels or milestones — resolve names to IDs, or infer
labels — even when the user does not say "Gitea".
Use when reading or writing Gitea labels or milestones — "create a label", "what labels does
this repo have", "close the milestone" — or to resolve label names to IDs, or infer a
Kind/Priority/Status label from context, 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.

View File

@@ -3,7 +3,9 @@ name: gitea-prs
description: >
Use when listing, reading, creating, updating, merging, or reviewing Gitea pull requests — even
when the user does not say "Gitea". Not issues -> `gitea-issues`.
when the user does not say "Gitea". A number the user names may be an issue or a PR — they share
one number space — so confirm which domain applies before dispatching. Not issues ->
`gitea-issues`. Not branch or commit operations -> `gitea-branches`.
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 when

View File

@@ -2,9 +2,11 @@
name: gitea-releases
description: >
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`.
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": "cut a v1.2.0",
"publish a prerelease", "tag this commit", "what's the latest release". Not branches or
commit history -> `gitea-branches`. Not issues -> `gitea-issues`. Not pull requests ->
`gitea-prs`.
compatibility: Requires Gitea MCP server configured with a token with write:repository scope, which
gates every release and tag tool here. Requires git remote "origin" pointing to the Gitea instance
@@ -55,7 +57,7 @@ If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea r
`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.
Pass a caller-supplied `tag_name` through verbatim — the API accepts any string, and semver with a `v` prefix is a tooling convention rather than a Gitea constraint.
Pass a caller-supplied `tag_name` through verbatim — the API accepts any string; semver is convention, not constraint.
## Step 3 — Procedure for the scenario in hand
@@ -63,7 +65,7 @@ These four are mutually exclusive — pick the one row the request lands on.
| Scenario | Procedure |
|---|---|
| Create a release | Call `create_release` with `tag_name`, `target`, `title`, and `is_draft`/`is_pre_release` set explicitly — never left to default. This surface carries no update or edit tool, so a wrong flag is repairable only by delete-and-recreate (`references/conventions.md`). A separate `create_tag` is only needed to tag a commit without wrapping it in a release. |
| Create a release | Call `create_release` with `tag_name`, `target`, `title`, and `is_draft`/`is_pre_release` set explicitly — never left to default. This surface carries no update or edit tool, so a wrong flag is repairable only by delete-and-recreate (`references/conventions.md`). A separate `create_tag` is only needed to tag a commit without wrapping it in a release — whether `create_release` creates a missing tag is unconfirmed, so verify with `get_tag`. |
| Delete a release | Resolve the numeric `id` per the first Gotcha, confirm intent, then call `delete_release`. The tag survives. |
| Delete 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. |
| List every page | Loop `page: 1, 2, 3...` until a response returns fewer than `per_page` entries. Nothing here auto-paginates. |

View File

@@ -12,11 +12,20 @@ additional tool schemas.
## Release wraps a tag, not the reverse
A release is a title, body (notes), and draft/prerelease flags layered on top of an existing or
newly-created tag. The tag is the git-level object (a name pointing at a commit); the release is a
Gitea-level metadata wrapper around it. This is why `delete_release` and `delete_tag` are separate
calls with separate identifiers (numeric id vs. tag name) — removing the wrapper never implies
removing the underlying pointer, and vice versa.
A release is a title, body (notes), and draft/prerelease flags layered on top of a tag. The tag is
the git-level object (a name pointing at a commit); the release is a Gitea-level metadata wrapper
around it. This is why `delete_release` and `delete_tag` are separate calls with separate
identifiers (numeric id vs. tag name) — removing the wrapper never implies removing the underlying
pointer, and vice versa.
### Whether `create_release` creates a missing tag is unconfirmed
`create_release` takes both `tag_name` and `target` (a commitish), and that shape *suggests* Gitea
creates the tag at `target` when `tag_name` does not already exist. That is inferred from the API
shape, not confirmed by any source this skill was built from (`references/sources.md`) — so treat it
as an assumption, not behaviour. When the caller depends on the tag existing, verify it afterward
with `get_tag` (or `list_tags`) rather than reporting it as created. `create_tag` is the only call
confirmed to create one.
## Semver tag naming

View File

@@ -5,7 +5,8 @@ description: >
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.
unambiguous PR request -> `gitea-prs`. Not local git work with no Gitea component ->
`git-workflow`.
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.