chore: drop the flat content mirror and native install support (ADR-0024)

apm becomes the only supported install path. The flat mirror at each plugin
root existed solely so Claude Code's native `claude plugin install` could
convention-scan plugin content (ADR-0017). With no native consumers, it cost
~20,000 tracked lines plus ~2,100 lines of sync tooling and ~88s of every
push to guard content apm never reads — and its only automated gate,
`claude plugin validate --strict`, passes on a plugin with zero content, so
it could not detect the defect ADR-0017 was created to fix.

Removes the mirror (213 files), the six per-plugin manifest pairs,
sync-plugin-content.sh, its 1,289-line test, the orphaned
marketplace-plugins.sh, and the check-plugin-content-sync and
validate-plugins pre-push hooks. The root `marketplace:` block and
.claude-plugin/ catalogue stay: apm's own marketplace consumers read that
same file, so `<name>@holocron` short names keep working.

tests/run-bats.sh now excludes .claude/skills/. apm installs from .apm/,
which carries the tests/ dirs the mirror stripped, so deployed .bats files
would otherwise be discovered and double-run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YR2CjVumUbEGWcMikcoXBD
This commit is contained in:
2026-09-14 16:59:42 +00:00
parent 0dffff3c21
commit 718c79af70
245 changed files with 298 additions and 22602 deletions

View File

@@ -1,21 +0,0 @@
{
"name": "gitea",
"version": "1.3.8",
"description": "Skills and agents for working with a Gitea forge through its HTTP API \u2014 the forge's own objects, as distinct from the local git clone.",
"author": {
"name": "Defame1297",
"email": "defame1297@rkdr.net",
"url": "https://git.dev.rkdr.net/Defame1297/"
},
"license": "MIT",
"homepage": "https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea",
"repository": "https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea",
"keywords": [
"gitea",
"issues",
"prs",
"milestones",
"releases",
"branches"
]
}

View File

@@ -1,21 +0,0 @@
{
"name": "gitea",
"version": "1.3.8",
"description": "Skills and agents for working with a Gitea forge through its HTTP API \u2014 the forge's own objects, as distinct from the local git clone.",
"author": {
"name": "Defame1297",
"email": "defame1297@rkdr.net",
"url": "https://git.dev.rkdr.net/Defame1297/"
},
"license": "MIT",
"homepage": "https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea",
"repository": "https://git.dev.rkdr.net/Defame1297/holocron/src/branch/main/plugins/gitea",
"keywords": [
"gitea",
"issues",
"prs",
"milestones",
"releases",
"branches"
]
}

View File

@@ -1,96 +0,0 @@
---
name: gitea-orchestrate
description: Orchestrates Gitea operations for other agents. Invoke when a caller needs a multi-step or destructive Gitea operation (merge a PR, delete a branch/release/tag/label/milestone, delete a file) coordinated across domain skills with safety gates, session context, and structured results.
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
- context7-gitea-tea-cli
disallowedTools: Edit, Write, NotebookEdit
---
You are the orchestrator for the gitea plugin — a composable workflow dispatcher designed for other agents to invoke multi-step Gitea operations reliably. Your one job is routing and safety-gating: you do not call `mcp__gitea__*` tools yourself, you delegate to domain skills and enforce confirmation on destructive operations. You never edit files. Every write you cause reaches its target through a domain skill's Gitea API call — never through an edit you make to the local working tree.
You resolve `owner`/`repo` once per session (via `rtk git remote -v` on `origin`) and carry that forward as session context to every domain skill you dispatch to, rather than making each skill re-resolve it.
**Scope:** this orchestrator routes Gitea-object operations across the six domain skills only: `gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, `gitea-releases`. `gitea-workflow` is also not routed here, but for a different reason than a missing domain: it is a human-facing conversational wrapper that gives status check-ins and resolves ambiguous bare numbers ("what's going on with #42") by reasoning about phrasing and context, and it composes the same six domain skills directly rather than calling this orchestrator. It is not a peer to invoke instead of this dispatcher — agent callers route Gitea-object operations here directly with an explicit `operation` field; direct human users to `gitea-workflow` when they want guided, conversational help. Never invoke `gitea-workflow` as an agent caller — resolve ambiguous issue/PR numbers yourself (see Number resolution below) instead of relying on its conversational disambiguation.
## Hard rules
These are non-negotiable regardless of `confirm` or any skill-local override:
- Never delete the repository's default branch (typically `main` or `master`) — refused outright, independent of `confirm`.
- `delete_release` takes a numeric `id`; `delete_tag` takes a `tag_name` string. These are asymmetric and never interchangeable — resolve the correct identifier via `list_releases`/`get_release` before calling either, and never guess one from the other.
- `rename-branch` is gated like a delete even though it destroys nothing: what a rename does to open PRs using the branch as head or base, to a matching protection rule, and to every other clone's tracking branch is unconfirmed by `gitea-branches`' sources. Require `confirm: true`, and verify the PR and protection sides afterwards.
- Deleting a release does not delete its tag, and vice versa — if the caller's intent is to remove both, dispatch both operations explicitly rather than assuming one implies the other.
- A 404 from any domain skill does not necessarily mean the target doesn't exist — Gitea hides permission errors as not-found. Surface this ambiguity in the error `code` (`not_found_or_forbidden`) rather than reporting a hard "does not exist."
- Label and milestone IDs must be resolved via `gitea-labels-milestones` before being applied to an issue or PR — never pass a label/milestone name directly to `gitea-issues`/`gitea-prs`, they require numeric IDs.
- Issues and PRs share one number space. Before dispatching an operation keyed on a bare number, resolve whether it's an issue or a PR yourself (see Number resolution) — never infer the domain from operation phrasing alone.
- `list_releases`/`list_tags` default to `per_page: 20` (other domains default to 30) with no server-side auto-pagination — when a caller needs a complete result set, loop `page` upward until a page returns fewer than `per_page` results before returning.
- Never commit secrets, credentials, or environment-specific config into any file written via `gitea-files`.
- You are read-only against the local working tree. Never create, edit, or delete a local file — not a manifest, not a config, not a scratch note. Local state is the caller's, and you only read it (e.g. `rtk git remote -v`) to resolve context.
### Number resolution
When an operation targets a bare issue/PR number and the caller hasn't specified which domain it is:
1. Dispatch to `gitea-issues` with `issue_read method: "get"` on that number.
2. Check the response's `is_pull` field: `true` → re-dispatch to `gitea-prs` for the actual operation; `false`/absent → it's an issue, proceed with `gitea-issues`.
3. Cache the resolution in session context for the remainder of the request so repeated references to the same number don't re-resolve.
4. If the resolution call 404s, do not conclude the number doesn't exist — return `not_found_or_forbidden` and suggest verifying token scope (`write:issue`).
Sub-skills carry their own local copies of relevant gotchas for humans who invoke them directly, bypassing this orchestrator. When a caller routes through you, this section is the enforcement backstop: check every routed operation against it before dispatch, not just the destructive-operation confirm gate below.
When invoked, you:
1. Parse the incoming workflow request (operation type, parameters, context overrides)
2. Check safety gates: if the operation is destructive (rename-branch, delete-branch, delete-release, delete-tag, delete-label, delete-milestone, delete-file, merge-pr) and the request lacks explicit `confirm: true`, fail immediately with "requires explicit confirmation"; deleting the default branch is refused outright regardless of `confirm`
3. Route to the appropriate domain skill: `gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, `gitea-releases`
4. Manage session context: resolve and carry forward `owner`/`repo` and any cached number-space resolutions, passing them explicitly to each skill
5. Handle error recovery: for recoverable failures (rate limiting, transient 5xx, pagination gaps) retry or complete the operation; for ambiguous 404s, attempt the permission-vs-not-found disambiguation before failing
6. Aggregate results and return structured JSON output suitable for agent chaining
## Inputs
- **operation:** string, one of:
- issues: list-issues, get-issue, create-issue, update-issue, comment-issue, search-issues
- labels/milestones: list-labels, create-label, update-label, delete-label, list-milestones, create-milestone, update-milestone, close-milestone, delete-milestone, resolve-labels
- prs: list-prs, get-pr, create-pr, update-pr, close-pr, reopen-pr, merge-pr, review-pr
- branches/commits: list-branches, create-branch, rename-branch, delete-branch, list-commits, get-commit
- files: get-file, get-dir, get-tree, write-file, delete-file
- releases/tags: list-releases, get-release, create-release, delete-release, list-tags, create-tag, delete-tag
- **parameters:** object, operation-specific arguments (issue/PR number, title, body, label names, tag name, file path, etc.)
- **context:** object (optional), session state to carry forward (`owner`, `repo`, cached number-space resolutions)
- **confirm:** boolean (optional), explicit confirmation for destructive operations (required if not set for rename-branch, delete-branch, delete-release, delete-tag, delete-label, delete-milestone, delete-file, merge-pr)
## Process
1. Validate the request structure and check if `operation` is known
2. Check the request against the Hard rules above (default-branch deletion, release/tag id-vs-name asymmetry, label/milestone ID resolution, number-space ambiguity, pagination) — refuse outright on violation, independent of `confirm`
3. If destructive operation: require `confirm: true`, else fail with structured "requires explicit confirmation" error
4. Resolve `owner`/`repo` via `rtk git remote -v` on `origin` if not already present in `context`, and reuse the resolution for the remainder of the request
5. If the operation targets a bare number and the domain isn't specified, run Number resolution above before dispatch
6. Invoke the appropriate domain skill via `Skill` with the operation, parameters, and resolved context (`owner`, `repo`)
7. Catch and handle Gitea errors: disambiguate 404s (not-found vs. permission-hidden), retry transient failures, loop pagination for `list_releases`/`list_tags` until exhausted
8. If recovery succeeds, continue; if not, return error structure with diagnostics and suggestions. If the blocker looks trivially fixable by a local edit — a stale `origin` URL, a malformed config, a missing label the repo obviously wants — name that fix in `suggestions` and stop. Do not act on it, and do not route it as a write operation the caller never asked for
9. Aggregate all outputs and return as structured JSON
## Output
```json
{
"status": "success" | "error",
"operation": "<operation_name>",
"result": {
"output": "<domain skill output or result>",
"context": { "owner": "...", "repo": "...", "resolved_number_type": "issue" | "pull" | null },
"applied_config": { "confirm_required": true | false }
},
"error": {
"message": "<human-readable error>",
"code": "<error type: not_found_or_forbidden | conflict | auth_failure | invalid_state | pagination_incomplete>",
"recovery_attempted": true | false,
"suggestions": ["<suggestion1>", "<suggestion2>"]
}
}
```

View File

@@ -1,60 +0,0 @@
---
name: gitea-branches
description: >
Use when listing, creating, renaming, or deleting branches in a Gitea repository,
or reading its commit history — "what commits are on this branch", "what changed
in that commit" — even when the user does not say "Gitea". Not a
local checkout's branches -> `git-branches`. Not local history ->
`git-history`. Not a PR's head or base -> `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 rename_branch, list_commits, and get_commit. Requires git remote "origin" pointing to the Gitea instance.
metadata:
category: integration
version: "0.1.3"
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
allowed-tools: Bash mcp__gitea__list_branches mcp__gitea__create_branch mcp__gitea__rename_branch mcp__gitea__delete_branch mcp__gitea__list_commits mcp__gitea__get_commit
---
## Gotchas
- **404 can mean 403.** Gitea masks permission errors as not-found — check token scope before reporting a branch or commit missing.
- **Nothing auto-paginates.** `list_branches`/`list_commits` return one page — iterate `page` until the count is below `per_page`.
- **`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
Extract `owner` and `repo` from the git remote before any tool call — `get_me` and `list_my_repos` are blocked under this skill's token scope, so the remote is the only source:
```bash
rtk git remote get-url origin
```
No origin, or not a Gitea URL: stop and report "No Gitea remote found — set origin to your Gitea instance URL."
## Step 2 — Dispatch
| Invocation | Action |
|---|---|
| `/gitea-branches` or `/gitea-branches list` | List branches |
| `/gitea-branches create <name> [from <base>]` | Create branch |
| `/gitea-branches rename <name> to <new-name>` | Rename branch |
| `/gitea-branches delete <name>` | Delete branch |
| `/gitea-branches commits [on <branch>] [touching <path>]` | List commit history |
| `/gitea-branches commit <sha>` | Get full detail for one commit |
For branch operations (list/create/rename/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
For reads: display branches as name + protected flag; display commits as SHA (short), message summary, author, date.
For writes (create/rename/delete): confirm the action taken, the branch name, and (for create) the base it forked from or (for rename) the name it had before.
For errors: surface the HTTP code and message, applying the 404 gotcha above before reporting "not found" to the user.

View File

@@ -1,107 +0,0 @@
---
topic: branches
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Branch operations
Call signatures below were verified live against the deployed `gitea-mcp` server via `ToolSearch`,
not copied from research docs — this is deliberate: research docs are generated from source code at
a point in time and can drift from the server actually deployed. **Last verified against gitea-mcp
v1.7.0**, as reported by `get_gitea_mcp_server_version`. Re-verify against the live schema if the
deployed version differs or these tools behave differently than documented here.
## `list_branches`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `page` (number, optional, default: `1`)
- `per_page` (number, optional, default: `30`)
**Call:**
```text
list_branches owner: <owner> repo: <repo>
```
**Response:** one object per branch: `name`, `protected` (bool), `commit_sha` (present when the
underlying commit data is available).
Paginate for the full list (see Gotchas) — iterate `page` until the count is less than `per_page`.
## `create_branch`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `branch` (string, required) — new branch name
- `old_branch` (string, optional) — source branch; if omitted, defaults to the repo's default
branch server-side (not necessarily your current local checkout)
**Call:**
```text
create_branch owner: <owner> repo: <repo> branch: <new-name> old_branch: <source-branch>
```
Default dispatch: if the user gives a base ("branch off of X", "from X"), pass it as `old_branch`.
If they don't specify a base and you're mid-task on a local branch, pass your current branch
(`rtk git branch --show-current`) as `old_branch` so the new branch forks from where you're actually
working, rather than silently falling back to the repo default. If neither applies (e.g. a fresh
top-level request with no working branch context), omit `old_branch` and let it default server-side.
A branch name collision returns `409 Conflict`.
## `rename_branch`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `branch` (string, required) — the branch's current name
- `new_name` (string, required) — the name to move it to
**Call:**
```text
rename_branch owner: <owner> repo: <repo> branch: <current-name> new_name: <new-name>
```
A rename moves the ref server-side; it is not a delete-plus-create, and no commit history is
rewritten. What it does to things *pointing at* the old name — open pull requests using it as head or
base, a branch protection rule matching it, CI config, and tracking branches on every other clone —
is **not confirmed** by this skill's sources: the deployed tool describes itself only as "Rename an
existing branch in a repository". Treat a rename of a branch with open PRs or a protection rule as a
change needing verification afterward (`list_branches`, plus `gitea-prs` for the PR side), and
confirm with the user first, exactly as for `delete_branch` below. A collision with an existing
branch name is expected to return `409 Conflict` by analogy with `create_branch`, not separately
confirmed.
## `delete_branch`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `branch` (string, required)
**Call:**
```text
delete_branch owner: <owner> repo: <repo> branch: <name>
```
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
protected, every time, regardless of how the request is phrased.
## Token scope
`list_branches`, `create_branch` and `delete_branch` all require `write:repository`. Gitea
gates reads behind write scope for repo-scoped operations, so `list_branches` needs the same scope
as the write operations, not `write:issue` alone. An earlier version of this doc claimed
`write:issue` alone was sufficient for `list_branches`, based on empirical testing under a token
that held both `write:issue` and `write:repository` simultaneously — that test didn't isolate the
variable, so it couldn't actually establish `write:issue` alone as sufficient.
`rename_branch` is a write on the same repo-scoped surface and is inferred to need `write:repository`
too — inferred by analogy, not separately confirmed.

View File

@@ -1,74 +0,0 @@
---
topic: commits
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Commit operations
Read-only commit history, scoped to a repo (optionally to one branch or one path). Call signatures
below were verified live against the deployed `gitea-mcp` server via `ToolSearch`, not copied from
research docs, for the same drift-avoidance reason noted in `references/branches.md`. **Last verified
against gitea-mcp v1.7.0**, as reported by `get_gitea_mcp_server_version`.
This domain has no prior skill precedent — it's new coverage added alongside branches because commit
history is naturally scoped to a branch (a "what happened on this branch" question), not because it
shares any tool family with branch create/delete.
## `list_commits`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `sha` (string, optional) — starting SHA or branch name; if omitted, gitea-mcp uses the repo's
default branch
- `path` (string, optional) — restrict results to commits that touched this file/path
- `page` (number, optional, default: `1`, minimum: `1`)
- `per_page` (number, optional, default: `30`, minimum: `1`)
**Call:**
```text
list_commits owner: <owner> repo: <repo> sha: <branch-or-sha> path: <optional-path>
```
Dispatch defaults:
- "commits on `<branch>`" → pass `<branch>` as `sha`.
- "commits touching `<path>`" (no branch mentioned) → pass `path` alone, `sha` omitted (defaults to
the repo's default branch).
- Both given → pass both; the result is history for that path, walked from that branch/SHA.
- Neither given → omit both; this returns default-branch history, which is a reasonable default for
an open-ended "what's the recent history here" question.
**Response:** one object per commit: `sha`, `html_url`, `created`, `message` (when available),
`author` (`{name, email, date}`, when available).
Paginate per the pagination Gotcha in SKILL.md if you need more than one page of history.
## `get_commit`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `sha` (string, required)
**Call:**
```text
get_commit owner: <owner> repo: <repo> sha: <commit-sha>
```
**Response:** same shape as a `list_commits` entry, but always fully populated (`message` and
`author` are guaranteed present, not conditional). Use this when the user asks about one specific
commit by SHA rather than browsing history — `list_commits` entries may omit `message`/`author` in
edge cases, `get_commit` will not.
## Token scope
Both tools are believed to require `write:repository`, even though they're read-only — inferred by
analogy with the scope-gating principle confirmed for branch operations in `branches.md`'s Token
scope section (Gitea gates reads behind write scope for repo-scoped operations), not a claim any
doc in this skill makes for commits by name: nothing here enumerates commits under
`write:repository` explicitly. An earlier version of this doc claimed `write:issue` alone worked, based on
empirical testing under a token that held both `write:issue` and `write:repository`
simultaneously — that test didn't isolate the variable either. Treat this as unverified until
tested under a token scoped to `write:issue` only (no `write:repository`).

View File

@@ -1,41 +0,0 @@
# Sources
**Note on call signatures:** per `docs/adr/0011-gitea-skill-deep-modules.md`, the tool parameter
signatures in `references/branches.md` and `references/commits.md` were re-verified live via
`ToolSearch` against the deployed `gitea-mcp` server — **last verified against v1.7.0**, as reported
by `get_gitea_mcp_server_version` — rather than copied verbatim from `api-reference.md` below. 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 research docs cited here informed gotchas, response shapes, and workflow context, not the
parameter lists themselves.
## gitea-mcp-repo
- **URL:** https://gitea.com/gitea/gitea-mcp
- **Description:** Official gitea-mcp repository; operation/*.go source files documenting the MCP tools, their parameters, and CLI flags. Extracted at v1.3.0; the parameter lists carried into this skill are re-verified live against the deployed server, last at v1.7.0. Informed the dispatch table and pagination / 404-may-mean-403 gotchas in SKILL.md, and the list/create/delete branch and list/get commit mechanics (including 409 conflict and default-branch fallback behavior) in references/branches.md and references/commits.md.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md, references/branches.md, references/commits.md
- **Status:** `extracted`
## gitea-mcp-slim-go
- **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/repo/slim.go
- **Description:** Slim response shape structs from gitea-mcp source, extracted at v1.3.0; defines exactly which fields the MCP server returns for branches (name, protected, commit_sha) and commits (sha, html_url, created, message, author), and informed get_commit's always-populated guarantee vs. list_commits' conditional fields.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** references/branches.md, references/commits.md
- **Status:** `extracted`
## context7-websites-gitea
- **URL:** context7:/websites/gitea
- **Description:** Official Gitea docs mirror on Context7 — informed the protected-branch gotcha in SKILL.md (protected branches can block server-side operations regardless of client-side checks; admins aren't exempt by default).
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md
- **Status:** `extracted`
## context7-gitea-tea-cli
- **URL:** context7:/git_gitea_com/gitea_tea
- **Description:** Official `tea` CLI docs on Context7 — practitioner conventions for issues, PRs, and releases (semver tags, draft/prerelease flags). Consulted as part of the shared research pass but its content is scoped to releases/tags, out of scope for branches/commits — no content from it was used in this skill.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** (none)
- **Status:** `extracted`

View File

@@ -1,47 +0,0 @@
---
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 (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
is not actually required for any of this domain's five tools.
metadata:
version: "1.0.1"
category: gitea
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
allowed-tools: mcp__gitea__get_file_contents mcp__gitea__get_dir_contents mcp__gitea__get_repository_tree mcp__gitea__create_or_update_file mcp__gitea__delete_file
---
## Gotchas
- **404 may mean an under-scoped token, not a missing path.** These tools gate on `write:repository`; check scope before concluding the path is missing.
- **Reads take `ref` (`tree_sha` on `get_repository_tree`), writes take `branch_name`.** One concept, three names — carry the wrong key and the branch is dropped.
- **`content` is base64 both ways — except under `withLines: true`.** Encode before a write, decode after a read; but with `withLines: true` `content` is already plain JSON text and the reported `"encoding": "base64"` is a lie. Decoding it yields garbage.
## Inputs
`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.
## Dispatch
Read the reference for the row you land on before making the call.
| Condition | Flow | Reference |
|---|---|---|
| Read one file, list one directory level, or walk the repository tree | Read | `references/reading.md` — the three read tools, their pagination behaviour, and why neither a directory listing nor a tree entry supplies the SHA a write needs |
| Create, update, or delete a file | Write | `references/writing.md` — 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.

View File

@@ -1,49 +0,0 @@
---
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 — **unless `withLines: true` was passed**, in which
case `content` is already plain text: a JSON array of `{"line": N, "content": "..."}` objects.
The response reports `"encoding": "base64"` either way, so that field is wrong under `withLines`
and decoding on its word yields garbage. 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 doesn't hold every entry — page with
`page`/`per_page` (defaults `1`/`30`) until a page returns fewer 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`; an under-scoped token gets 404 instead of 403 so the
endpoint doesn't leak whether the resource exists. On a path you're confident about, check token
scope before concluding it doesn't exist.

View File

@@ -1,48 +0,0 @@
# Sources
## gitea-mcp-repo
**Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags. Tool parameters and SHA/concurrency behavior were cross-checked live against the deployed MCP tool schemas via `ToolSearch`, per this repo's process for resolving schema-vs-docs drift, rather than copied from the derived research doc.
**Source:** https://gitea.com/gitea/gitea-mcp
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:**
- 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
**Description:** Slim response shape structs from gitea-mcp source; defines exactly which fields the MCP server returns for files (top-level `sha`, no nested `content.sha`) and directory/tree entries.
**Source:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/repo/slim.go
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:**
- 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
**Description:** Official Gitea docs mirror on Context7 (docs.gitea.com content) — confirms direct-commit file editing through the web UI is a first-class, expected workflow rather than an API-only escape hatch.
**Source:** context7:/websites/gitea
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
**Contributing files:**
- SKILL.md (Handoff — committing straight to a branch is the normal path, not an escape hatch)
## context7-gitea-tea-cli
**Description:** Official `tea` CLI (reference Gitea client) docs on Context7 — practitioner command patterns for issues, PRs, and releases, including semver tag/release conventions, draft/prerelease flags, and release-notes-from-file conventions. Consulted alongside `context7-websites-gitea` while researching `workflow-conventions.md` (both sources contribute to that research doc, backing `gitea-workflow`); its file-command patterns did not end up informing any gitea-files content.
**Source:** context7:/git_gitea_com/gitea_tea
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** (none)

View File

@@ -1,79 +0,0 @@
---
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: <branch>, path)` → read the top-level `sha`.
2. `create_or_update_file(owner, repo, path, content, message, branch_name, sha: <that value>)`.
## Delete a file
Same SHA-first pattern, with no create-style fallback. `sha` is schema-**required** on
`delete_file`, unlike `create_or_update_file` where omitting it means *create* — so an omitted `sha`
is rejected client-side by input validation and the call never reaches Gitea. The HTTP 422 that is
actually reachable here is the stale-`sha` case.
1. `get_file_contents(owner, repo, ref: <branch>, path)` → read the top-level `sha`.
2. `delete_file(owner, repo, path, message, branch_name, sha: <that value>)`.
## Worked sequence — new file on a new branch, then a PR
```text
1. create_or_update_file
owner, repo
path: "docs/example.md"
content: "<base64-encoded 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 | Stale `sha` — the file changed between the read and the write | 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 |
| Client-side input-validation error naming `sha` | `sha` omitted on `delete_file`, where it is schema-required | Fetch the current SHA and retry — nothing was sent to Gitea |
| 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.

View File

@@ -1,74 +0,0 @@
---
name: gitea-issues
description: >
Use when reading or writing Gitea issues — "create an issue", "what issues are open",
"close issue #N", "comment on issue #N", "label 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
scopes. Requires git remote "origin" pointing to the Gitea instance for owner/repo resolution
when invoked directly by a human; an orchestrating caller (e.g. gitea-workflow) may pass
owner/repo already resolved.
metadata:
category: integration
version: "0.1.5"
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
- context7-gitea-tea-cli
allowed-tools: Bash mcp__gitea__list_issues mcp__gitea__issue_read mcp__gitea__issue_write mcp__gitea__search_issues
---
## Gotchas
- **`list_issues` mixes in PRs unless you filter.** Issues and PRs share one number space; pass `type: "issues"` to exclude PRs (or `"pulls"`). `is_pull` is returned only by `issue_read method: "get"` — on a list item the only tell is `html_url`'s path segment (`/issues/` vs `/pulls/`).
- **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 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.
- **404 may mean 403.** Gitea hides permission errors as not-found — check `write:issue` scope before concluding the issue doesn't exist.
## Step 1 — Resolve owner and repo
Skip if an orchestrating caller already passed `owner`/`repo` in, or the action is `search` (cross-repository, needs only a query). Otherwise, before any tool call:
```bash
rtk git remote get-url origin
```
No origin, or not a Gitea URL: stop and report "No Gitea remote found — set origin to your Gitea instance URL."
## Step 2 — Dispatch
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.
| Invocation | Flow | Read |
|---|---|---|
| `/gitea-issues` or `/gitea-issues list` | List issues with `type: "issues"` so PRs are excluded, optionally filtered by state | `references/issues.md` |
| `/gitea-issues <N>` | Get one issue, routing to `gitea-prs` when the number turns out to be a PR | `references/issues.md` |
| `/gitea-issues <N> comments` | Get an issue's comments | `references/issues.md` |
| `/gitea-issues <N> labels` | Get an issue's labels as full objects, IDs included | `references/issues.md` |
| `/gitea-issues close <N>` | Close an issue by updating its state | `references/issues.md` |
| `/gitea-issues comment <N>` | 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 <N>` | 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 <query>` | Cross-repo search, narrowed by owner, state, type or labels | `references/search.md` |
## Step 3 — Create
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.
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
Reads: a compact table or numbered list — number, title, state, labels, milestone.
Writes: what was created or updated, with the issue number and the URL when one is returned.
Errors: the HTTP code and message as returned, without paraphrasing either.

View File

@@ -1,91 +0,0 @@
---
topic: enrichments
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
---
# Create-flow enrichments
Closes out the four enrichments deferred from issue #6 comment #848. Run in this order before the
`issue_write method: "create"` call in SKILL.md's create dispatch: **labels → milestone →
assignee → dependency link**. Each step is independent and skippable on its own — a missing
milestone fit doesn't block label inference, and so on — but this is the order that lets later
steps use context established earlier (e.g. the inferred `Kind/*` label can inform which milestone
plausibly fits).
**Cross-skill composition note:** none of the steps below read `gitea-labels-milestones`'s
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.
## 1. Label inference
Delegate the entire signal-to-label mapping to `gitea-labels-milestones` — this skill does not
duplicate the `Kind/*`/`Priority/*`/`Status/*` taxonomy table.
1. Compose `gitea-labels-milestones` to resolve labels for the issue being created: give it the
draft title/body and ask it to infer and resolve applicable labels (it calls
`label_read method: "list_repo_labels"` internally and runs its own inference procedure).
2. Take back the resolved label IDs (and which scope groups, if any, need replacing — not relevant
yet on a brand-new issue, since there's nothing to replace).
3. If `gitea-labels-milestones` reports low confidence and omits a `Kind/*` label, pass no `Kind/*`
ID rather than guessing one yourself. `Priority/Medium` is the one label its own inference
procedure defaults to when no urgency signal is present — that's expected, not a gap.
4. Pass the resulting label IDs to `issue_write`'s `labels` parameter (omit the parameter entirely
if the resolved list is empty).
## 2. Milestone assignment on create
1. Compose `gitea-labels-milestones` to list open milestones (it calls
`milestone_read method: "list" state: "open"` internally).
2. Compare the issue's inferred scope (title, body, and any `Kind/*` label from step 1) against
each open milestone's title/description. Assign a milestone only when the fit is clear — a
milestone literally named for the feature area, or one whose description explicitly covers this
kind of work. A milestone that's merely "the current one" without a clear scope match is not a
confident fit.
3. If a milestone clearly fits, pass its **numeric ID** (never the title) as `issue_write`'s
`milestone` parameter. `issue_write`'s `milestone` field only accepts the ID — see
`references/issues.md`.
4. If no milestone clearly fits, omit `milestone` entirely. Guessing a milestone assignment is worse
than leaving it unset — an issue can always be milestoned later, but a wrong milestone
assignment pollutes that milestone's issue count and scope.
## 3. Assignee on create — the `get_me` workaround
`issue_write` accepts `assignees: [<login>]`, but there is no way to discover the *current user's*
own login to self-assign: `get_me` requires the `read:user` scope, and this repo's Gitea MCP token is
scoped to `write:issue` + `write:repository` only, with no `read:user` grant. This is a hard
capability gap, not something to work around with a guess.
**Workaround:** support an optional user-configured default assignee login, supplied one of two
ways:
- A config value the caller or orchestrator (e.g. `gitea-workflow`) already resolved and passes in
when invoking this skill.
- A login explicitly stated in the conversation ("assign this to alice") — use that login directly,
no lookup needed, since Gitea accepts a login string without requiring you to resolve an ID first.
**If neither is available, omit `assignees` entirely.** Do not guess a login, do not fail the create
over a missing assignee, and do not attempt `get_me`/`search_users` as a fallback — both are blocked
by the same scope gap and will only produce a confusing secondary error.
## 4. Dependency-linking convention
gitea-mcp has no native issue-dependency field (no "blocks"/"blocked by" relationship in the API
surface this skill has access to). The convention is to write **"Depends on #N"** as a line in the
issue body.
This is not just a text convention with no effect — Gitea auto-renders `#N` (and `!N` for PRs) as a
real clickable cross-reference with no separate API call, a documented platform behavior (see
`references/sources.md` for the backing research). This works because issues and PRs share one
repo-scoped number space. Use the bare `#N` form for same-repo dependencies; use `owner/repo#N` for
a dependency in a different repo.
When creating an issue that depends on another, append a line like:
```text
Depends on #42
```
to the body before calling `issue_write method: "create"`. No separate field or follow-up call is
involved — the rendering happens automatically once the body is saved.

View File

@@ -1,136 +0,0 @@
---
topic: issues
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Issue operations
Call signatures below were verified live against the deployed `gitea-mcp` server via `ToolSearch`,
not copied from `api-reference.md` — this is deliberate: research docs are generated from source at
a point in time and can drift from the server actually deployed. Last verified against gitea-mcp
**v1.7.0**, as reported by `get_gitea_mcp_server_version`. Drift runs in both directions: this file
previously recorded `list_issues` as having neither a `type` nor a `milestones` parameter, and
v1.7.0 has both. Re-verify against the live schema if these tools appear to behave differently than
documented here.
## `list_issues`
**Parameters (live schema):**
- `owner` (string, required)
- `repo` (string, required)
- `state` (string, optional, default `"all"`) — conventional values `"open"`/`"closed"`/`"all"`, not
schema-enforced as an enum
- `labels` (array of strings, optional) — filter by label *name* (not ID)
- `milestones` (array of strings, optional) — filter by milestone name or numeric ID, both passed as
strings
- `type` (string, enum `"issues"` | `"pulls"`, optional) — omit it and the response mixes both
- `since` (string, optional) — ISO 8601, issues updated after this time
- `before` (string, optional) — ISO 8601, issues updated before this time
- `page` (number, optional, default `1`)
- `per_page` (number, optional, default `30`)
**Pass `type: "issues"` on any listing meant to show issues.** Issues and PRs share one repo number
space and the unfiltered response interleaves them; the only thing distinguishing them on a list
item is the `html_url` path segment (`/issues/` vs `/pulls/`), since `is_pull` is not returned on
list items — see the Gotchas section of SKILL.md.
**Call:**
```text
list_issues owner: <owner> repo: <repo> state: "open" type: "issues"
```
**Response (list item):** `number`, `title`, `state`, `html_url`, `user`, `comments`, `created_at`,
`updated_at`, and optionally `labels` (`[]string`), `milestone` (`{id, title}`), `ref`, `deadline`.
Body and `closed_at` are omitted from list responses — call `issue_read method: "get"` for those.
Paginate: `page`/`per_page`, stop once the count is below `per_page`.
## `issue_read`
**Parameters (live schema, matches `api-reference.md`):**
- `method` (string, required, enum) — `"get"` | `"get_comments"` | `"get_labels"`
- `owner` (string, required)
- `repo` (string, required)
- `issue_number` (number, required)
**`get`** — full issue: `number`, `title`, `body`, `state`, `html_url`, `user`, `labels`
(`[]string`), `comments`, `created_at`, `updated_at`, `closed_at`, and optionally `assignees`
(`[]string`), `milestone` (`{id, title}`), `ref`, `deadline`, `is_pull` (present only when this
number is backed by a pull request — absent, not `false`, on true issues).
**`get_comments`** — array of `{id, body, user, html_url, created_at, updated_at}`.
**`get_labels`** — array of full label objects (`id`, `name`, `color`, `description` — not slimmed
to name strings, unlike the labels array on `get`).
**Call:**
```text
issue_read method: "get" owner: <owner> repo: <repo> issue_number: <N>
```
Always check `is_pull` before treating a number as a plain issue — see the shared number-space
gotcha in SKILL.md.
## `issue_write`
**Parameters (live schema, matches `api-reference.md`):**
- `method` (string, required, enum) — `"create"` | `"update"` | `"add_comment"` | `"edit_comment"` |
`"add_labels"` | `"remove_label"` | `"replace_labels"` | `"clear_labels"`
- `owner` (string, required)
- `repo` (string, required)
- `issue_number` (number, required for every method except `"create"`)
- `title` (string, required for `"create"`)
- `body` (string, required for `"create"`, `"add_comment"`, `"edit_comment"`)
- `assignees` (array of strings, optional) — login names (see `references/enrichments.md` for why
this is usually omitted)
- `milestone` (number, optional) — milestone ID, never a title
- `state` (string, enum `"open"`/`"closed"`/`"all"`, optional) — for `"update"`
- `commentID` (number, optional, required for `"edit_comment"`)
- `labels` (array of numbers, optional) — label IDs, never names — for `add_labels`/`replace_labels`
- `label_id` (number, optional, required for `"remove_label"`) — singular, not the array form
- `ref` (string, optional) — branch association, informational only
- `deadline` (string, optional) — ISO 8601
- `remove_deadline` (boolean, optional)
**Create:**
```text
issue_write method: "create"
owner: <owner> repo: <repo>
title: <title> body: <body>
labels: [<resolved IDs>] ← omit if none confidently inferred
milestone: <resolved ID> ← omit if none clearly fits
assignees: ["<login>"] ← omit if no default configured
```
**Close:**
```text
issue_write method: "update" owner: <owner> repo: <repo> issue_number: <N> state: "closed"
```
No `method: "close"` exists — using one errors.
**Comment:**
```text
issue_write method: "add_comment" owner: <owner> repo: <repo> issue_number: <N> body: <text>
```
**Apply resolved label IDs directly** (bypassing `references/enrichments.md`'s inference step, e.g.
when the caller already named exact labels):
```text
issue_write method: "add_labels" owner: <owner> repo: <repo> issue_number: <N> labels: [<IDs>]
```
To replace all labels atomically instead of adding: `method: "replace_labels"`.
To remove one: `method: "remove_label" label_id: <single ID>`.
**Default to `add_labels`.** `replace_labels` clears every label not in the array, so it drops
labels the caller never mentioned. Reach for it only when the caller asked for the issue's label
set to become exactly what they listed. In particular, do not use it to enforce one-label-per-scope:
exclusivity is a per-label property — the server drops the sibling itself for a label whose
`exclusive` field is `true`, and a label whose `exclusive` is `false` (every `Kind/*` on this
instance) is legitimately stackable. See `gitea-labels-milestones` for how to read that field.
## Token scope
All of `list_issues`, `issue_read`, and `issue_write` are verified working under a token holding
`write:issue` + `write:repository`.

View File

@@ -1,39 +0,0 @@
---
topic: search
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# `search_issues`
Call signature verified live against the deployed `gitea-mcp` server via `ToolSearch` at authoring
time (see `references/sources.md`) — confirmed to match `api-reference.md`.
**Parameters:**
- `query` (string, required) — the only hard-required parameter
- `state` (string, enum `"open"` | `"closed"` | `"all"`, optional)
- `type` (string, enum `"issues"` | `"pulls"`, optional) — the same filter, with the same values,
that `list_issues` takes (see `references/issues.md`)
- `labels` (string, optional) — comma-separated label **names** — a plain string, not the array form
`list_issues` uses
- `owner` (string, optional) — restrict results to one owner
- `page` (number, optional, default `1`)
- `per_page` (number, optional, default `30`)
**Call:**
```text
search_issues query: <text>
```
**Narrowing the search:**
```text
search_issues query: <text> owner: <owner> state: "open" type: "pulls" labels: "bug,urgent"
```
This is a cross-repository search (unlike `list_issues`, which is scoped to one `owner`/`repo`) —
useful when the caller doesn't know which repo an issue lives in, or wants results across an
organization. Pass `owner` to narrow scope if the caller does know it.
Paginate the same way as `list_issues`: iterate `page` until the returned count is less than
`per_page`.

View File

@@ -1,42 +0,0 @@
# Sources
**Note on call signatures:** per `docs/adr/0011-gitea-skill-deep-modules.md`, the tool parameter
signatures in `references/issues.md` and `references/search.md` are re-verified live via
`ToolSearch` against the deployed `gitea-mcp` server — 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. That re-verification is
ongoing, not one-off: an earlier live check recorded `list_issues` as lacking the `type` and
`milestones` parameters `api-reference.md` documents, and both are present on the deployed
gitea-mcp **v1.7.0**, which is the version these signatures are current as of.
## 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.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md, references/issues.md, references/search.md, references/enrichments.md
- **Status:** `extracted`
## gitea-mcp-slim-go
- **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/issue/slim.go
- **Description:** Slim response shape structs from gitea-mcp source; defines exactly which fields the MCP server returns for issues (label name-vs-ID slimming, milestone object-vs-string shape, `is_pull` presence on single-item reads only).
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md, references/issues.md
- **Status:** `extracted`
## context7-websites-gitea
- **URL:** context7:/websites/gitea
- **Description:** Official Gitea docs mirror on Context7 (docs.gitea.com content) — backs the automatic cross-reference rendering (`#N`/`!N`) that validates the "Depends on #N" dependency-linking convention.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** references/enrichments.md
- **Status:** `extracted`
## context7-gitea-tea-cli
- **URL:** context7:/git_gitea_com/gitea_tea
- **Description:** Official `tea` CLI (reference Gitea client) docs on Context7 — practitioner command patterns for issues, PRs, and releases. Consulted alongside context7-websites-gitea while researching `workflow-conventions.md`'s cross-reference-linking section (both sources contribute to that research doc); its issue-specific command patterns did not end up informing any gitea-issues content beyond what context7-websites-gitea already backs.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** (none)
- **Status:** `extracted`

View File

@@ -1,63 +0,0 @@
---
name: gitea-labels-milestones
description: >
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.
metadata:
category: integration
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
- context7-gitea-tea-cli
version: "0.1.6"
allowed-tools: Bash mcp__gitea__label_read mcp__gitea__label_write mcp__gitea__milestone_read mcp__gitea__milestone_write
---
## Gotchas
- **Applying a label needs a numeric ID; issue/PR responses give only name strings.** Resolve name → ID with `label_read` first.
- **`pull_request_read` returns `milestone` as a bare title string, `issue_read` as `{id, title}`.** Recover the ID by listing milestones and matching the title.
- **Never assume a `Kind/*`/`Priority/*`/`Status/*` scope is exclusive — read each label's `exclusive` field.** `list_repo_labels` always returns it; `label_write` can only set it via org create methods ("org only"). `true` means Gitea enforces one-per-scope; replacing instead of stacking on a `false` label destroys a valid one.
## Step 1 — Resolve owner, repo and org
Extract `owner` and `repo` from the git remote before any tool call (skip if an orchestrating caller already passed them in):
```bash
rtk git remote get-url origin
```
No origin, or 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's the org name whenever the owner is an organisation, which the remote URL doesn't say.
Read the failure text before interpreting it: `list_org_labels` needs `read:organization`, which this skill's declared scopes don't carry, so it fails with a scope error *before* it ever checks org-vs-user. Report the unchecked scope, not an empty pool — only a genuine not-found means the owner is a user account with no org pool.
## 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"` |
| Create a repo/org label | `label_write` | `"create_repo_label"` / `"create_org_label"` |
| Edit a repo/org label | `label_write` | `"edit_repo_label"` / `"edit_org_label"` |
| Delete a repo/org label | `label_write` | `"delete_repo_label"` / `"delete_org_label"` |
| List milestones | `milestone_read` | `"list"` |
| Get one milestone by ID | `milestone_read` | `"get"` |
| Create a milestone | `milestone_write` | `"create"` |
| Update / close a milestone | `milestone_write` | `"update"` |
| Delete a milestone | `milestone_write` | `"delete"` |
Every list method paginates manually (`per_page` default 30) — iterate `page: 1, 2, ...` until a page returns fewer than `per_page`. A truncated list silently breaks name → ID resolution.
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`.

View File

@@ -1,81 +0,0 @@
---
topic: label-inference
source_keys:
- context7-websites-gitea
- gitea-mcp-repo
---
# Label inference guide
Maps context-pattern signals from conversation content (an issue being drafted, a bug report, a PR
description) to this repo's `Kind/*` / `Priority/*` / `Status/*` label taxonomy. Used by
`gitea-issues` and `gitea-prs` before creating or updating an issue/PR, and directly when the user
asks to label something without naming exact labels.
## Branching on exclusivity
`references/labels.md` owns the exclusivity rule and the read-versus-write asymmetry behind it. Read
it there rather than assuming a scope's behaviour from its name. Inference needs only the branch:
carry each candidate label's own `exclusive` value forward from the resolution call and act on it.
- **`exclusive: true`** — the server drops the sibling on write. Add the label and let it; do not
pre-remove the label already there, and do not compute a replacement set client-side. Inferring
`Priority/High` onto an issue carrying `Priority/Medium` needs no special handling.
- **`exclusive: false`** — **add alongside, never replace.** Stripping a co-existing label in the
same scope destroys a valid one: an issue can legitimately carry `Kind/Bug` and `Kind/Security`
at once.
This skill enforces no client-side exclusivity convention of its own.
## Signal → label mapping
**`Kind/*`** (what kind of work this is):
| Signal in context | Label |
|---|---|
| Bug report, error, crash, unexpected behavior, "broken", "doesn't work" | `Kind/Bug` |
| New capability, "add support for", net-new functionality | `Kind/Feature` |
| Improvement to existing behavior, "make X better", refactor with behavior change | `Kind/Enhancement` |
| Docs-only change, README/comment/guide updates | `Kind/Documentation` |
| Vulnerability, credential exposure, injection risk, auth bypass | `Kind/Security` |
| Test coverage, "add tests for X", a missing or flaky test, a test-only change | `Kind/Testing` |
**`Priority/*`** (urgency):
| Signal in context | Label |
|---|---|
| "blocking", "critical", "urgent", production-down | `Priority/Critical` |
| "soon", "high priority", "should do this sprint" | `Priority/High` |
| "low priority", "nice to have", "whenever", explicitly deferred | `Priority/Low` |
| No urgency signal present | `Priority/Medium` (default) |
**`Status/*`** (workflow state):
| Signal in context | Label |
|---|---|
| Explicit statement that the work is blocked on something else | `Status/Blocked` |
The label names in all three tables are the taxonomy this guide was written against; none of them is
guaranteed to exist on the target repo. Step 2 below resolves every inferred name against the live
label set, and a name that does not resolve is reported rather than substituted.
## Procedure
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 and each label's `exclusive` value — inference must never guess an ID, only a
name, then resolve it. Both pools can apply to one issue, so 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. Read that call's failure text: a
`required=[read:organization]` scope error means the org pool was never queried — report the
missing token scope rather than reporting the label unresolved. Only a not-found response means
there is no org pool and the name is genuinely absent.
3. Match inferred label names against the resolved list (case-insensitive) and carry each match's
`exclusive` value forward: `true` means the server replaces the sibling on write, `false` means
the label is added alongside whatever is already applied (see above).
4. **Low-confidence inference omits the label.** If no signal confidently maps to a `Kind/*` value,
do not guess — omit `Kind/*` entirely rather than default to one. `Priority/Medium` is the one
exception: it's the explicit default when no urgency signal is present, not a guess.
5. Hand the resolved IDs, each with its `exclusive` value, to the caller's `issue_write`/
`pull_request_write` call — this skill does not apply labels to an issue or PR itself.

View File

@@ -1,122 +0,0 @@
---
topic: labels
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
---
# Label operations
Execution detail for `label_read` and `label_write`. Both tools operate on either **repo-scoped**
or **org-scoped** labels — never both in one call. Pick the method family (`*_repo_label*` vs.
`*_org_label*`) that matches the target, and pass `owner`+`repo` or `org` accordingly.
## Verified live schemas
`label_read` — required: `method`.
| Param | Type | Notes |
|---|---|---|
| `method` | string (enum) | `"list_repo_labels"` \| `"get_repo_label"` \| `"list_org_labels"` |
| `owner` | string | for repo methods |
| `repo` | string | for repo methods |
| `org` | string | for org methods |
| `id` | number | label ID, required for `"get_repo_label"` |
| `page` | number | default `1` |
| `per_page` | number | default `30` |
`label_write` — required: `method`.
| Param | Type | Notes |
|---|---|---|
| `method` | string (enum) | `"create_repo_label"` \| `"edit_repo_label"` \| `"delete_repo_label"` \| `"create_org_label"` \| `"edit_org_label"` \| `"delete_org_label"` |
| `owner` | string | for repo methods |
| `repo` | string | for repo methods |
| `org` | string | for org methods |
| `id` | number | for edit/delete |
| `name` | string | required for create |
| `color` | string | hex `#RRGGBB`, required for create |
| `description` | string | optional |
| `exclusive` | boolean | accepted as a *write* param on org creates only — repo labels still carry and enforce `exclusive`, set outside this tool surface |
| `is_archived` | boolean | repo labels only |
Note: unlike `milestone_read`/`milestone_write`, `owner`/`repo`/`org` are **not** schema-required on
either label tool — only `method` is. Passing none for a repo/org method still fails, just as a
runtime error from Gitea rather than a client-side validation error.
## List repo labels
```text
label_read method: "list_repo_labels" owner: <owner> repo: <repo> per_page: 50
```
Paginate (`page: 1, 2, ...`) until the count is below `per_page` — the only way to build a complete
name → ID map, since there's no lookup-by-name endpoint.
Every returned repo label carries its own `exclusive` boolean, so exclusivity is always *readable*
per repo label. That does not contradict `label_write`'s schema, which annotates `exclusive` as
"(org only)": reading and setting are different questions, and only the setting half is org-scoped
(see "Create a label" below). Where the field is `true` Gitea enforces one-label-per-scope
server-side; where it is `false` labels in that scope stack legitimately. Read the field — never
infer exclusivity from the `/` in a name, and never carry another repo's map over.
On the instance this skill was authored against (`Defame1297/holocron`) the split ran: every
`Priority/*`, `Reviewed/*` and `Status/*` label `exclusive: true`, every `Kind/*` label and
`Compat/Breaking` `exclusive: false`. That is one repo's configuration at one point in time, recorded
as a worked example of what the field looks like in practice — it is not a property of the taxonomy
and says nothing about the repo you are called against.
## Get one label
```text
label_read method: "get_repo_label" owner: <owner> repo: <repo> id: <id>
```
## Resolve a name to an ID
The tool surface carries no lookup-by-name method. List all repo labels (paginating if needed),
scan for a 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. Its failure modes are not interchangeable. `token does not have at least
one of required scope(s), required=[read:organization]` means the org pool was never queried — report
that missing scope rather than reporting the label unresolved. Only a not-found response means the
owner is a user account with no org pool, making the miss 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
```text
label_write method: "create_repo_label"
owner: <owner> repo: <repo>
name: "Kind/Bug"
color: "#d73a4a"
description: "Confirmed bug"
```
For an org label, use `method: "create_org_label"` with `org:` instead of `owner`/`repo`, and
`exclusive: true` if the label belongs to a mutually-exclusive scope group. `label_write` accepts
`exclusive` on org methods only, so a repo label's exclusivity cannot be set or cleared through this
tool — it is set in the Gitea UI or against the REST API directly, and read back via
`list_repo_labels`.
## Edit a label
```text
label_write method: "edit_repo_label" owner: <owner> repo: <repo> id: <id> color: "#ff0000"
```
Only pass the fields being changed — `id` plus any of `name`/`color`/`description`/`is_archived`.
## Delete a label
```text
label_write method: "delete_repo_label" owner: <owner> repo: <repo> id: <id>
```
Deleting a label does not remove it from historical issue/PR timeline events — it disappears only
from current label lists.

View File

@@ -1,98 +0,0 @@
---
topic: milestones
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Milestone operations
Execution detail for `milestone_read` and `milestone_write`. Milestones are always repo-scoped —
there is no org-level milestone concept, unlike labels.
## Verified live schemas
`milestone_read` — required: `method`, `owner`, `repo`.
| Param | Type | Notes |
|---|---|---|
| `method` | string (enum) | `"get"` \| `"list"` |
| `owner` | string | required |
| `repo` | string | required |
| `id` | number | milestone ID, required for `"get"` |
| `name` | string | title filter, for `"list"` |
| `state` | string | default `"all"` — conventional values `"open"`/`"closed"`/`"all"`, but **not enforced by an enum in the live schema** (plain string). Any other value is passed through to Gitea rather than rejected client-side. |
| `page` | number | default `1` |
| `per_page` | number | default `30` |
`milestone_write` — required: `method`, `owner`, `repo`.
| Param | Type | Notes |
|---|---|---|
| `method` | string (enum) | `"create"` \| `"update"` \| `"edit"` \| `"delete"` — `"update"`/`"edit"` are aliases for the same operation; prefer `"update"` |
| `owner` | string | required |
| `repo` | string | required |
| `id` | number | required for update/delete |
| `title` | string | required for create |
| `description` | string | optional |
| `due_on` | string | due date — the live tool schema only describes this as an opaque "due date" string with no enforced format; ISO 8601 (e.g. `"2025-03-01T00:00:00Z"`) is the conventional value Gitea's REST API accepts, not something confirmed by the live MCP schema itself |
| `state` | string (enum) | `"open"` \| `"closed"` — **this one is schema-enforced**, unlike `milestone_read`'s `state` |
Note: unlike `label_read`/`label_write`, both milestone tools hard-require `owner` and `repo` at the
schema level — there's no scope variant to omit them for.
## List milestones
```text
milestone_read method: "list" owner: <owner> repo: <repo> state: "open"
```
Report each as: id, title, state, due date, open/closed issue counts.
## Get one milestone
```text
milestone_read method: "get" owner: <owner> repo: <repo> id: <id>
```
## Resolve a milestone ID from a title
Needed whenever the only handle available is a title — e.g. a `pull_request_read` response, which
returns `milestone` as a bare title string rather than `{id, title}`. Call:
```text
milestone_read method: "list" owner: <owner> repo: <repo> name: <title>
```
and take the `id` of the matching result. If `name` filtering returns no match (e.g. due to a
title typo or case mismatch), fall back to listing without the filter and matching manually.
## Create a milestone
```text
milestone_write method: "create"
owner: <owner> repo: <repo>
title: "v1.0"
description: "First stable release"
due_on: "2025-03-01T00:00:00Z"
```
Report the returned ID — the caller (`gitea-issues`/`gitea-prs`) needs it to assign issues/PRs to
this milestone via `issue_write`/`pull_request_write`.
## Update or close a milestone
```text
milestone_write method: "update" owner: <owner> repo: <repo> id: <id> state: "closed"
```
Only pass the fields being changed — `id` plus any of `title`/`description`/`due_on`/`state`.
## Delete a milestone
```text
milestone_write method: "delete" owner: <owner> repo: <repo> id: <id>
```
Deleting a milestone does not delete or unassign the issues/PRs that referenced it — they simply
lose the milestone reference.

View File

@@ -1,33 +0,0 @@
# Sources
## 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
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md, references/labels.md, references/milestones.md, references/label-inference.md
- **Status:** `extracted`
## gitea-mcp-slim-go
- **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/issue/slim.go, https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/pull/slim.go, https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/repo/slim.go
- **Description:** Slim response shape structs from gitea-mcp source; defines exactly which fields the MCP server returns for issues, PRs, branches, commits, tags, releases, and files — including the label name-vs-ID and milestone object-vs-string representation quirks this skill's Gotchas document
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md, references/labels.md, references/milestones.md
- **Status:** `extracted`
## context7-websites-gitea
- **URL:** context7:/websites/gitea
- **Description:** Official Gitea docs mirror on Context7 (docs.gitea.com content) — scoped/exclusive label conventions and milestone/label state-transition semantics
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md, references/labels.md, references/label-inference.md
- **Status:** `extracted`
## context7-gitea-tea-cli
- **URL:** context7:/git_gitea_com/gitea_tea
- **Description:** Official `tea` CLI (reference Gitea client) docs on Context7 — practitioner command patterns for labels and milestones
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** SKILL.md
- **Status:** `extracted`

View File

@@ -1,60 +0,0 @@
---
name: gitea-prs
description: >
Use when listing, reading, creating, updating, merging, or reviewing Gitea pull requests — even
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
invoked directly by a human; an orchestrating caller (e.g. gitea-workflow) may pass owner/repo
already resolved.
metadata:
category: integration
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
- context7-gitea-tea-cli
version: "0.1.4"
allowed-tools: Bash 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.** `#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.
## Step 1 — Resolve owner and repo
Extract from the git remote before any tool call, skipping this when an orchestrating caller already passed them in:
```bash
rtk git remote get-url origin
```
No origin, or not a Gitea URL: stop and report "No Gitea remote found — set origin to your Gitea instance URL."
## Step 2 — Dispatch
Confirm the number names a PR, not an issue, before writing to it.
| 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` |
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.
## Step 3 — 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.

View File

@@ -1,41 +0,0 @@
---
topic: merging
source_keys:
- gitea-mcp-repo
- context7-websites-gitea
- context7-gitea-tea-cli
---
# Merge workflow
## Two independent gates
Before merging, two things can each independently block the merge, and only one of them is visible from `get_status`:
1. **CI status** — `pull_request_read method: "get_status"` returns the combined commit status for the PR's head commit. This reflects CI/build checks only.
2. **Review and branch-protection state** — required approval counts, an allowlist of required approvers/teams, whether requested reviewers have reviewed, and stale-approval handling (an approval given before new commits were pushed may be auto-dismissed or ignored, depending on repo settings). None of this is exposed via `get_status` — it is enforced server-side by the merge call itself, and repository admins are not exempt from it by default (an explicit "administrators must follow branch protection" setting is what removes that bypass).
A green `get_status` does not mean the merge will succeed. Treat both gates as independently checkable, and expect `pull_request_write method: "merge"` to return an error if branch-protection requirements aren't met even when CI is passing.
## `pull_request_write method: "merge"`
**Parameters** (in addition to `owner`, `repo`, `pull_number` — see `references/pull-requests.md` for the full parameter list):
- `merge_style` (string, optional, default `"merge"`) — `"merge"` | `"rebase"` | `"rebase-merge"` | `"squash"` | `"fast-forward-only"`
- `title` (string, optional) — override the merge/squash commit's title
- `message` (string, optional) — merge commit message (for squash, this becomes the squash commit's message — see the auto-close gotcha below)
- `delete_branch` (boolean, optional) — delete the head branch after a successful merge
- `force_merge` (boolean, optional) — merge even if checks are failing; use only when the user explicitly asks to override a failing CI status
- `merge_when_checks_succeed` (boolean, optional) — queue the merge to happen automatically once checks pass, instead of merging immediately
- `head_commit_id` (string, optional) — expected head SHA; supplying it lets the server detect if the branch moved since you last read it (conflict detection) rather than merging a stale diff
## Recommended sequence
1. Call `pull_request_read method: "get_status"` first. Report CI status to the user. Do not block the merge attempt solely because CI is failing unless the user asks you to stop — but do surface it before proceeding.
2. Call `pull_request_write method: "merge"` with the chosen `merge_style` (ask the user if unspecified; do not default to squash silently, since squash has the closing-keyword implication below) and `delete_branch: true` unless told otherwise.
3. If branch-protection requirements block the merge, the call itself will return the error — report it verbatim rather than retrying with `force_merge` unless the user explicitly asks to override.
## Merging does not auto-close linked issues
Gitea has no GitHub-style "merge triggers close" event. It does parse closing keywords (`Fixes #N`, `Closes #N`) in commit messages that land on the default branch — so a regular (non-squash) merge, which preserves each original commit message, can auto-close an issue this way if one of those commits used a closing keyword. A squash merge rewrites history into a single commit, so whether the keyword survives depends entirely on what `message` (or the default squash message) ends up being.
After any merge, call `issue_read method: "get"` (in `gitea-issues`) on any issue the PR references before deciding whether to close it explicitly. Closing an already-closed issue is a harmless no-op, but don't assume a manual close is always needed, and don't assume it never is.

View File

@@ -1,84 +0,0 @@
---
topic: pull-requests
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# 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. 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`
**Parameters:**
- `owner` (string, required)
- `repo` (string, required)
- `state` (string, optional, default `"all"`) — `"open"` | `"closed"` | `"all"`
- `sort` (string, optional, default `"recentupdate"`) — `"oldest"` | `"recentupdate"` | `"leastupdate"` | `"mostcomment"` | `"leastcomment"` | `"priority"`
- `milestone` (number, optional) — milestone ID filter, not a title
- `page` (number, optional, default 1)
- `per_page` (number, optional, default 30)
List responses trim PRs down to summary fields — `head`/`base` are bare ref strings and `milestone` does not appear as an object. Don't rely on this call to resolve a milestone's ID; see the Gotcha below.
## `pull_request_read`
**Parameters:**
- `method` (string, required) — `"get"` | `"get_diff"` | `"get_files"` | `"get_status"` | `"get_reviews"` | `"get_review"` | `"get_review_comments"`
- `owner` (string, required)
- `repo` (string, required)
- `pull_number` (number, required)
- `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}`); and `review_comments`, *when present*, as an integer count rather than comment objects — it was absent from a live `"get"` on a PR with no inline comments, so verify the key before reading it (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`).
**Milestone gotcha:** because `pull_request_read` only ever returns a milestone title, never an ID, resolving "which milestone ID does this PR belong to" requires calling into `gitea-labels-milestones`' `milestone_read method: "list"` and matching by title. Do not attempt to infer or guess the ID. This is the read-side milestone problem; for the separate write-side one, see the `"create"` drop Gotcha under `pull_request_write`.
## `pull_request_write`
**Parameters:**
- `method` (string, required) — `"create"` | `"update"` | `"close"` | `"reopen"` | `"merge"` | `"update_branch"` | `"add_reviewers"` | `"remove_reviewers"`
- `owner` (string, required)
- `repo` (string, required)
- `pull_number` (number, required for every method except `"create"` — this is enforced by convention, not by the tool's schema, so passing it incorrectly produces a runtime error rather than a client-side validation error)
- `title` (string, required for `"create"`; optional for `"update"`)
- `body` (string, required for `"create"`; optional for `"update"`)
- `head` (string, required for `"create"`) — source branch; same-repo PRs use a bare branch name, cross-repo fork PRs use `"fork-owner:branch-name"` (see Gotcha)
- `base` (string, required for `"create"`; optional for `"update"`) — target branch; passing it on `"update"` retargets an open PR onto a different base
- `assignee` (string, optional) — single login; **`"update"` only** — silently dropped on `"create"` (see Gotcha)
- `assignees` (array of strings, optional) — login names; **`"update"` only** — silently dropped on `"create"` (see Gotcha)
- `milestone` (number, optional) — milestone ID, never a title; **`"update"` only** — silently dropped on `"create"` (see Gotcha)
- `state` (string, optional, for `"update"`) — `"open"` | `"closed"` (no `"all"` — unlike issue state filters)
- `allow_maintainer_edit` (boolean, optional, for `"update"`)
- `labels` (array of numbers, optional) — label IDs, never names — resolve via `gitea-labels-milestones` first
- `deadline` (string, optional) — ISO 8601
- `remove_deadline` (boolean, optional) — **`"update"` only** — silently dropped on `"create"` (harmless there, but it does not do anything)
- `reviewers` (array of strings, optional) — login names; **`"add_reviewers"`/`"remove_reviewers"` only** — silently dropped on both `"create"` and `"update"` (see Gotcha)
- `team_reviewers` (array of strings, optional) — same as `reviewers`: `"add_reviewers"`/`"remove_reviewers"` only
- `draft` (boolean, optional) — prepends `"WIP:"` to the title; honoured on both `"create"` and `"update"` (see Gotcha)
Merge-specific parameters (`merge_style`, `delete_branch`, `force_merge`, `merge_when_checks_succeed`, `head_commit_id`, `message` as merge commit message) are covered in `references/merging.md`.
**`"create"` silently drops most optional parameters.** `"create"` reads only `owner`, `repo`, `title`, `body`, `head`, `base`, `draft`, `labels`, and `deadline`. Every other optional parameter — including `assignee`, `assignees`, `milestone`, `reviewers`, `team_reviewers` and `remove_deadline` — is accepted without error and discarded. Nothing marks the drop: no error, no warning, and nothing in the response distinguishing a dropped parameter from one that was never passed — the response simply omits the key. Setting any of them requires a second call after the PR exists — `"update"` for `assignee`/`assignees`/`milestone`, `"add_reviewers"` for `reviewers`/`team_reviewers`.
Two things make this easy to miss:
- **`labels` does apply on `"create"`.** It sits next to `milestone` in the parameter list and reads identically, so a caller who confirms the labels landed will reasonably assume the milestone did too.
- **`issue_write method: "create"` does honour `assignees` and `milestone`.** The asymmetry is specific to pull requests; experience with the issue write does not transfer.
To verify a milestone actually applied, re-read the PR and check for the `milestone` key, or check that the milestone's `open_issues` count moved — the create response alone cannot tell you.
**Cross-repo head format:** `head` must be `"fork-owner:branch-name"` for a PR originating from a fork of the base repo. Passing a bare branch name causes Gitea to search for that branch in the base repo instead, and returns 422 when it isn't found there.
**Draft/WIP behavior:** `draft: true` on `"create"` is implemented by prepending `"WIP:"` to the title — there is no first-class draft boolean stored separately from the title. The title returned by subsequent reads will include the prefix. To un-draft, call `"update"` with `draft: false` and no `title` — the server reads the PR's current title and strips the prefix itself, so you do not need to reconstruct the un-prefixed text. Two prefixes are recognised for stripping — `WIP:` and `[WIP]` — matched case-insensitively, even though only `"WIP: "` is ever added. Passing `title` alongside `draft` applies the prefix rule to the title you passed rather than the stored one.
**`update_branch`:** takes only `owner`, `repo`, `pull_number` — no other parameters. It merges the current base branch into the PR's head branch server-side, resolving a PR that Gitea reports as behind its base (analogous to GitHub's "Update branch" button). Use it when a PR shows `mergeable: false` or an out-of-date status due to base-branch drift rather than an actual conflict; if the head and base have truly diverged with conflicting changes, this call fails and the conflict must be resolved by pushing a merge/rebase to the head branch directly, outside this skill's scope.

View File

@@ -1,54 +0,0 @@
---
topic: reviews
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# 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`. Last verified against **v1.7.0**, as reported by `get_gitea_mcp_server_version`.
## Review state machine
A review is not a single write. It moves through states:
1. **`create`** — opens a review in `"PENDING"` state, optionally attaching inline comments. Nothing is visible to other users yet.
2. **`submit`** — finalizes the pending review with a terminal `state`: `"APPROVED"`, `"REQUEST_CHANGES"`, or `"COMMENT"`. This is the point at which the review becomes visible and counts toward merge-gate requirements (see `references/merging.md`).
3. **`dismiss`** — invalidates an already-submitted review (e.g. an approval that's no longer valid after force-push), with an optional `message` giving the reason. Dismissal does not delete the review record — it stays visible but marked dismissed.
4. **`delete`** — removes a review outright. Use only for a review that was never submitted (e.g. abandoning a `PENDING` draft); do not use `delete` to retract a submitted review — use `dismiss` instead.
## `pull_request_review_write`
**Parameters:**
- `method` (string, required) — `"create"` | `"submit"` | `"delete"` | `"dismiss"` | `"reply_comment"` | `"resolve_thread"` | `"unresolve_thread"`
- `owner` (string, required)
- `repo` (string, required)
- `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) — 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` — 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_comments` on the `"get"` response is a count, not the comments — and it may be absent.** Where the full PR object returned by `pull_request_read method: "get"` carries `review_comments`, it is an integer: the number of inline review comments. Presence is not guaranteed. A live `"get"` against a PR with zero inline comments carried no such key at all — only `comments`, which counts issue-style comments, not review ones. Treat it as present only when non-zero, and check for the key before reading it rather than assuming the response shape. Either way 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.

View File

@@ -1,33 +0,0 @@
# Sources
## gitea-mcp-repo
- **URL:** https://gitea.com/gitea/gitea-mcp
- **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`
## 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 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`
## context7-websites-gitea
- **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:** references/merging.md
- **Status:** `extracted`
## context7-gitea-tea-cli
- **URL:** context7:/git_gitea_com/gitea_tea
- **Description:** Official `tea` CLI (reference Gitea client) docs on Context7 — practitioner command patterns for PR review workflows.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** references/merging.md
- **Status:** `extracted`

View File

@@ -1,73 +0,0 @@
---
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": "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
for owner/repo resolution, unless an orchestrating caller passes them already resolved.
metadata:
category: integration
version: "0.1.2"
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
- context7-gitea-tea-cli
allowed-tools: Bash mcp__gitea__list_releases mcp__gitea__get_release mcp__gitea__get_latest_release mcp__gitea__create_release mcp__gitea__delete_release mcp__gitea__list_tags mcp__gitea__get_tag mcp__gitea__create_tag mcp__gitea__delete_tag
---
## Gotchas
- **`delete_release` takes the numeric `id`, never a `tag_name`; `delete_tag` takes the tag name, never an id.** Holding only a tag name, resolve the release id through `list_releases` or `get_release` first — a tag name passed to `delete_release` fails, and that failure is not evidence the release is already gone.
- **Deleting a release never deletes its tag**, and the reverse direction is *unconfirmed* — verify with `list_releases`/`get_release` after `delete_tag`. Removing both takes two independent destructive calls.
- **Set `is_draft`/`is_pre_release` explicitly on every `create_release`** — Gitea infers neither from a `-beta`/`-rc` tag name, so `v2.0.0-beta.1` publishes as a full release and becomes the repo's latest. `draft`/`prerelease` are output field names only; passing `draft` as an input key is silently ignored.
- **`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 pages.
## Step 1 — Resolve owner and repo
`owner` and `repo` are required on every tool below. Extract from the git remote, unless an orchestrating caller already passed them in:
```bash
rtk git remote get-url origin
```
No origin, or not a Gitea URL: stop and report "No Gitea remote found — set origin to your Gitea instance URL."
## Step 2 — Dispatch
| Action | Tool | Required params | Optional params |
|---|---|---|---|
| List releases | `list_releases` | `owner`, `repo` | `is_draft`, `is_pre_release`, `page` (default 1), `per_page` (default 20) |
| Get one release | `get_release` | `owner`, `repo`, `id` (number) | — |
| Get latest release | `get_latest_release` | `owner`, `repo` | — |
| Create release | `create_release` | `owner`, `repo`, `tag_name`, `target`, `title` | `body`, `is_draft`, `is_pre_release` |
| Delete release | `delete_release` | `owner`, `repo`, `id` (number) | — |
| List tags | `list_tags` | `owner`, `repo` | `page` (default 1), `per_page` (default 20) |
| Get one tag | `get_tag` | `owner`, `repo`, `tag_name` | — |
| 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.
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
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 — 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 auto-paginates. |
If exact input params or response field shapes are needed, read `references/call-signatures.md`. If the caller raises semver tag naming, draft/prerelease semantics, release-notes sourcing, or how a release relates to its tag, read `references/conventions.md`.

View File

@@ -1,75 +0,0 @@
---
topic: call-signatures
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
---
# Release and tag call signatures
Signatures and response shapes are derived from gitea-mcp source (`operation/*.go` and `slim.go`,
see `references/sources.md`) rather than copied from upstream API docs, which can drift from the
deployed gitea-mcp version — but this is a source-code extraction, not a live MCP tool call.
Input parameter schemas for all 9 tools here were additionally cross-checked live via `ToolSearch`
against the deployed `mcp__gitea__*` tools and confirmed to match exactly — required and optional
params, names, and defaults. Last verified against gitea-mcp **v1.7.0**, as reported by
`get_gitea_mcp_server_version`. That check covers input params only: the response shapes below
remain source-derived, not live-verified, so re-verify them if a response reads differently than
documented here.
`owner` and `repo` are required strings on every tool below and are omitted from the per-tool lists
for brevity.
## Releases
**`list_releases`**
- Optional: `is_draft` (boolean), `is_pre_release` (boolean), `page` (number, default 1), `per_page` (number, default 20)
- Returns an array of release objects (shape below), one page at a time.
**`get_release`**
- Required: `id` (number) — the release's numeric id, not its tag name.
- Returns a single release object.
**`get_latest_release`**
- No parameters beyond `owner`/`repo`.
- Returns a single release object for the most recently published release. The deployed tool's own description reads "the most recent published (non-draft) release" — it names drafts as excluded and is silent on prereleases, so whether prereleases are also excluded is *unconfirmed*; verify with `list_releases` if the caller depends on it. Either way a release created without `is_pre_release: true` is eligible, which is why that flag has to be set in the create call (see `conventions.md`).
**`create_release`**
- Required: `tag_name` (string), `target` (string — branch, tag, or commit SHA to cut the tag from), `title` (string)
- Optional: `body` (string — release notes), `is_draft` (boolean), `is_pre_release` (boolean)
- Assumed (not confirmed by the research docs) that if `tag_name` doesn't already exist as a tag, Gitea creates it against `target` as part of this call. Verify with `get_tag`/`list_tags` afterward if the caller needs certainty.
**`delete_release`**
- Required: `id` (number) — same numeric id as `get_release`. Does not accept `tag_name`.
- Does not delete the underlying tag.
**Release object shape** (returned by list/get/create/latest):
```text
id, tag_name, target, title, body, draft, prerelease, html_url, author, created_at, published_at
```
`author` is the creator's login. `body` holds the release notes.
## Tags
**`list_tags`**
- Optional: `page` (number, default 1), `per_page` (number, default 20)
- Returns an array of `{ name, commit_sha }` — no `message` field on list responses.
**`get_tag`**
- Required: `tag_name` (string)
- Returns `{ name, message, commit_sha }` — the only tag call that returns `message`.
**`create_tag`**
- Required: `tag_name` (string)
- Optional: `target` (string — commitish to tag; if omitted, Gitea tags the default branch tip), `message` (string — annotated tag message)
**`delete_tag`**
- Required: `tag_name` (string). Does not accept a numeric id.
- Assumed by symmetry with `delete_release` (documented above as not deleting the underlying tag) to also not delete any release wrapping the tag — but this reverse direction is not independently confirmed by the research docs, and is the more dangerous direction to get wrong: an agent might skip an explicit `delete_release` call assuming the release survives. Verify with `list_releases`/`get_release` after calling `delete_tag` rather than assume.
## Pagination
Nothing auto-paginates. Loop `page: 1, 2, ...` until a page returns fewer items than `per_page`.
`list_releases`/`list_tags` default `per_page` to 20, not the usual 30 — assuming 30 under-counts
pages needed.

View File

@@ -1,60 +0,0 @@
---
topic: conventions
source_keys:
- context7-websites-gitea
- context7-gitea-tea-cli
---
# Release and tag conventions
Practitioner conventions that inform *how* to use the mechanics in `call-signatures.md` — not
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 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
Per the `tea` CLI (the reference Gitea client), tag names conventionally follow semver with a `v`
prefix: `v1.2.0`, `v2.0.0-beta.1`. This is a convention observed by tooling and humans, not a
Gitea-enforced constraint — the API accepts any string as `tag_name`. Don't validate or rewrite a
caller-supplied tag name against semver; just pass it through.
## Draft and prerelease are explicit flags
`is_draft` and `is_pre_release` are booleans the caller sets directly on `create_release` — Gitea
does not infer either from the tag name, even though the `-beta`/`-rc` suffix convention above is
commonly used to signal a prerelease to humans. When a user asks to "cut a beta" or "publish a
release candidate," set `is_pre_release: true` explicitly in the same call rather than relying on
the tag string to carry that meaning.
Getting this wrong is not cheaply repairable. The gitea-mcp release surface is `create_release`,
`get_release`, `get_latest_release`, `list_releases` and `delete_release` — there is **no update or
edit tool**, so a published release's flags cannot be corrected in place. The only remedy is
`delete_release` plus a fresh `create_release`, a destructive round trip; meanwhile a beta published
without `is_pre_release` is the release `get_latest_release` returns. Set the flags in the create
call.
Note the input/output naming mismatch: the input param is `is_draft`, but the release object
returned by the API uses `draft` (and `prerelease`) as the field names. `draft` is never a valid
input key — passing `draft: true` to `create_release` is silently ignored rather than erroring.
## Release notes sourcing
Practitioner convention (per `tea`) is to source release notes (`body`) from a changelog file
rather than typing them inline for each release — useful context when a caller asks to "generate"
or "use the changelog for" release notes rather than write them from scratch.

View File

@@ -1,47 +0,0 @@
# Sources
## gitea-mcp-repo
- **URL:** https://gitea.com/gitea/gitea-mcp
- **Description:** Official gitea-mcp repository; operation/*.go source files documenting the MCP tools, their parameters, and CLI flags. Originally extracted at v1.3.0; the input parameter schemas in `references/call-signatures.md` were re-verified live via `ToolSearch` against the deployed server, **last verified at v1.7.0** as reported by `get_gitea_mcp_server_version`.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/api-reference.md (Releases and Tags section); plugins/gitea/docs/research/docs/gitea/troubleshooting.md (`delete_release` numeric-id gotcha, `per_page` defaults)
**Contributing files:**
- SKILL.md (Dispatch table, Gotchas)
- references/call-signatures.md
**Status:** `extracted`
## gitea-mcp-slim-go
- **URL:** https://gitea.com/gitea/gitea-mcp/raw/branch/main/operation/repo/slim.go
- **Description:** Slim response shape structs from gitea-mcp source; defines exactly which fields the MCP server returns for tags and releases.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/api-reference.md (Releases and Tags response shapes)
**Contributing files:**
- references/call-signatures.md (release/tag object shapes)
**Status:** `extracted`
## context7-websites-gitea
- **URL:** context7:/websites/gitea
- **Description:** Official Gitea docs mirror on Context7 (docs.gitea.com content) — release and tag semantics, draft/prerelease behavior.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/workflow-conventions.md (Release and tag conventions section)
**Contributing files:**
- SKILL.md (Gotchas — draft/prerelease as explicit flags)
- references/conventions.md
**Status:** `extracted`
## context7-gitea-tea-cli
- **URL:** context7:/git_gitea_com/gitea_tea
- **Description:** Official `tea` CLI (reference Gitea client) docs on Context7 — practitioner release/tag command patterns, semver tag conventions, draft/prerelease flags, release-notes-from-file conventions.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/workflow-conventions.md (Release and tag conventions section)
**Contributing files:**
- references/conventions.md (semver tag naming, release-notes sourcing)
**Status:** `extracted`

View File

@@ -1,43 +0,0 @@
---
name: gitea-workflow
description: >
Use when a human wants an ambiguous Gitea status check — a no-args check-in, a bare number
asked *about* without saying issue or PR ("status of #42"), or a capability whose owning skill
is unclear. Not an agent caller -> `gitea-orchestrate`. Not a stated action on a number
("close #42") -> `gitea-issues`. Not an unambiguous PR request -> `gitea-prs`. Not local-only
git -> `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.
metadata:
category: integration
version: "0.1.4"
source_keys:
- gitea-mcp-repo
- gitea-mcp-slim-go
- context7-websites-gitea
---
## Gotchas
- **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.
## Dispatch
The invocation's shape selects exactly one branch.
| Invocation shape | Flow | Reference |
|---|---|---|
| No arguments, no specific request | Repo status check-in | `references/status-checkin.md` |
| A bare number the user asks *about*, with neither "issue" nor "PR" said and no action stated | 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` |
A bare number carrying a stated action ("close #42", "merge #42", "label #42") is not row 2: that is a write, and row 2 only presents detail. Resolve the domain per `references/number-resolution.md`, then hand the action to `gitea-issues` or `gitea-prs` to perform.
Read only the reference file matching the selected branch — each is self-contained for its concern.
## Report
Every branch ends here. Present results in plain language; the branch's reference file carries its format.

View File

@@ -1,21 +0,0 @@
---
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. A 404 here isn't proof the number doesn't exist — Gitea hides permission errors as not-found (see `gitea-issues` Gotchas). Report the 404 and suggest checking the token's `write:issue` scope rather than reporting "no such issue or PR."
If the user stated an action on the number rather than asking about it, resolution is only step one: hand the action, with the resolved domain, to `gitea-issues` or `gitea-prs` to carry out. Presenting detail is not a substitute for performing the write.
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.

View File

@@ -1,22 +0,0 @@
---
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/rename/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.

View File

@@ -1,33 +0,0 @@
# Sources
## 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`), 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:** references/status-checkin.md, references/number-resolution.md, references/skill-index.md
- **Status:** `extracted`
## gitea-mcp-slim-go
- **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:** references/number-resolution.md
- **Status:** `extracted`
## context7-websites-gitea
- **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:** references/number-resolution.md
- **Status:** `extracted`
## context7-gitea-tea-cli
- **URL:** context7:/git_gitea_com/gitea_tea
- **Description:** Official `tea` CLI docs on Context7 — practitioner conventions for issues, PRs, and releases. Consulted as part of the shared research pass but its content is domain-specific (PR/release workflow patterns), out of scope for pure status/routing behavior — no content from it was used in this skill.
- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md
- **Contributing files:** (none)
- **Status:** `extracted`

View File

@@ -1,15 +0,0 @@
---
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.