fix(gitea): correct label-scope and closing-keyword claims in issues/labels skills

A documentation-accuracy audit against plugins/gitea/docs/research/docs/gitea/*.md
found three overclaims/unsupported claims in skill content:

- gitea-labels-milestones: reworded the exclusive-flag gotcha and
  label-inference.md's "scoped labels" section — exclusive is documented as
  org-labels-only (api-reference.md, data-model.md) and unsupported by the
  live create_repo_label/edit_repo_label schema, so repo-level exclusivity
  for Kind/*/Priority/*/Status/* is a client-side convention this skill
  enforces via replace_labels, not a server guarantee.
- gitea-labels-milestones: added a "Step 1 — Resolve owner and repo" section
  (git remote get-url origin) and Bash to allowed-tools, since
  milestone_read/milestone_write and repo-scoped label_read/label_write
  hard-require owner+repo and the skill is documented as directly invokable,
  matching the pattern already in gitea-issues/SKILL.md.
- gitea-issues: softened the claim that Gitea parses Fixes #N/Closes #N in
  commit messages to auto-close issues — no research doc supports this, and
  examples.md states issues are not auto-closed on PR merge. Now notes the
  behavior is plausible but unconfirmed, keeping the issue_read re-check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VwtxcDXuLZxYzWnT2FaoZU
This commit is contained in:
2026-07-05 19:09:55 +00:00
parent 940e414980
commit 1cd496e208
3 changed files with 22 additions and 8 deletions

View File

@@ -35,7 +35,7 @@ allowed-tools: Bash mcp__gitea__list_issues mcp__gitea__issue_read mcp__gitea__i
- **`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. - **`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. - **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.) - **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.)
- **A closing keyword in a commit message can auto-close an issue without any `issue_write` call.** Gitea has no GitHub-style "merge closes issue" event, but it does parse `Fixes #N`/`Closes #N` in commit messages landing on the default branch. After a PR merges (a `gitea-prs` operation), 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. - **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`. - **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. - **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.

View File

@@ -21,9 +21,9 @@ metadata:
- gitea-mcp-slim-go - gitea-mcp-slim-go
- context7-websites-gitea - context7-websites-gitea
- context7-gitea-tea-cli - context7-gitea-tea-cli
version: "0.1.0" version: "0.1.1"
allowed-tools: mcp__gitea__label_read mcp__gitea__label_write mcp__gitea__milestone_read mcp__gitea__milestone_write allowed-tools: Bash mcp__gitea__label_read mcp__gitea__label_write mcp__gitea__milestone_read mcp__gitea__milestone_write
--- ---
## Gotchas ## Gotchas
@@ -33,11 +33,21 @@ allowed-tools: mcp__gitea__label_read mcp__gitea__label_write mcp__gitea__milest
- **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. - **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. - **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`. - **`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`.
- **A `/` in a label name plus `exclusive: true` means mutual exclusivity, not just a naming convention.** This repo's `Kind/*`, `Priority/*`, `Status/*` labels follow Gitea's native scoped-label feature: applying a new label within a scope (e.g. `Priority/High`) is expected to replace any existing label in that same scope, not add alongside it. Label inference (see `references/label-inference.md`) must respect this — replace, don't stack. - **`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. - **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. - **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.
## Dispatch ## 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):
```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."
## Step 2 — Dispatch
| Task | Tool | method | | Task | Tool | method |
|---|---|---| |---|---|---|

View File

@@ -14,9 +14,13 @@ asks to label something without naming exact labels.
## Scoped labels are mutually exclusive — replace, don't stack ## Scoped labels are mutually exclusive — replace, don't stack
Each of `Kind/*`, `Priority/*`, `Status/*` is a Gitea scoped-label group (the `/` delimiter plus Each of `Kind/*`, `Priority/*`, `Status/*` is treated as a scoped-label group by convention (the `/`
`exclusive: true` on the label). Applying a new label within a scope is expected to replace any delimiter naming pattern). Gitea's `exclusive` flag — the mechanism that would let the server itself
existing label in that same scope on the target issue/PR, not add alongside it. When inference enforce one-label-per-scope — is documented as an org-labels-only setting, and the repo-level
`label_write` methods used here don't accept it at all. So exclusivity within these scopes is a
convention this skill enforces client-side, not something the server guarantees: applying a new
label within a scope is expected to replace any existing label in that same scope on the target
issue/PR, not add alongside it. When inference
selects a `Priority/High` label and the issue already carries `Priority/Medium`, the write should selects a `Priority/High` label and the issue already carries `Priority/Medium`, the write should
result in only `Priority/High` remaining — use `replace_labels` scoped to that group's labels, or at result in only `Priority/High` remaining — use `replace_labels` scoped to that group's labels, or at
minimum remove the superseded label before adding the new one. Never leave two labels from the same minimum remove the superseded label before adding the new one. Never leave two labels from the same