diff --git a/AGENTS.md b/AGENTS.md index 92dfab3..7719ed9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,3 +31,5 @@ This repo is built by a junior developer as a homelab tool intended to scale to - **Don't hand a fork a TaskList that includes governance-gated actions** (push, publish, merge) unless you are prepared for it to act on those items without a fresh confirmation round. A fork acting on its own initiative is not party to any pending human confirmation the coordinator is mid-flow on, so it can bypass a gate that was meant to hold. - **`TaskGet`/`TaskUpdate`/`TaskList` only work for forks.** Fresh (non-fork) subagents cannot discover or call these tools. When delegating to a fresh subagent, the coordinator owns all task-list bookkeeping itself — claim and complete the entry on the agent's behalf — rather than instructing the fresh agent to self-claim or self-complete. - **Worktree/branch cleanup is part of closing out the PR, not a separate step.** When a coordinator creates a worktree (`Agent(isolation: "worktree")` or `git worktree add` directly) to land a PR, merging that PR is not the end of the task. Immediately after verifying the merge: run `git worktree remove --force --force ` (the double `-f` is required whenever the worktree initialized submodules to run tests — assume it did, this repo has several), then `git branch -d` both the feature branch and any `worktree-agent-` isolation branch the `Agent` tool auto-created for that worktree — `git worktree remove` deletes neither branch on its own. Do this without waiting for the user to notice stale branches/worktrees and ask. +- **`Agent(isolation: "worktree")` may fork from `main`, not the branch you were on.** Don't assume the isolated worktree is based on your current branch just because that's what you asked for — in practice it has forked from a stale `main` (missing commits the task depended on) even when the coordinator was on a feature branch at call time. Every affected agent has to notice (missing files, unexpected diff base) and self-correct with `git merge --ff-only ` or a reset onto `origin/` before it can safely edit. Don't leave this to chance: tell the agent explicitly in the prompt which branch its worktree must be based on and to verify/rebase onto it as a first step before editing anything, and check for this yourself when reviewing a worktree agent's report. +- **Don't route already-fully-specified corrective edits through `kyberforge:forge`.** `forge` exists to classify ambiguous "what should I build" intent before routing to an author skill — it isn't needed when the coordinator already knows the exact file, line, and fix. Sending fully-specified fixes through `forge` adds a grill-and-delegate layer that can itself spawn further sub-delegation (forked sub-subagents), which has been observed to lose track of hard constraints handed down the chain (e.g. "don't commit yet," "edit in this worktree") because each hop re-derives instructions from a shorter brief. Call the target author skill (`skill-author`, `plugin-author`, etc.) directly for known fixes; reserve `forge` for genuinely undecided "which artifact type is this" questions. diff --git a/docs/adr/0011-gitea-skill-deep-modules.md b/docs/adr/0011-gitea-skill-deep-modules.md new file mode 100644 index 0000000..971cc35 --- /dev/null +++ b/docs/adr/0011-gitea-skill-deep-modules.md @@ -0,0 +1,109 @@ +# Gitea skill splits into deep modules under `plugins/gitea/`, replacing the flat `plugins/bin/skills/gitea/` + +The gitea skill originated under kyberforge (`b9c73cc`), moved to `plugins/bin/skills/gitea/` +(`4f603cd`), and covers only 5 of gitea-mcp's ~15 tool domains (issues, labels, milestones, PRs, +branches) in one flat `SKILL.md` mixing routing logic with execution detail. Meanwhile +`plugins/gitea/` already existed as a plugin scaffold holding comprehensive research docs (all 55 +MCP tool schemas, code-derived from gitea-mcp source, at +`plugins/gitea/docs/research/docs/gitea/`) but empty `skills/`, `agents/`, and `.mcp.json`. This +ADR records the decisions from a grill-with-docs session on issue #6 that splits the flat skill +into deep modules and relocates it to `plugins/gitea/`. + +**Relocation.** The new deep-module skill structure is built in `plugins/gitea/`, not +`plugins/bin/`, making the gitea plugin self-contained — bundling its own skills, agents, and MCP +config — matching this repo's Plugin glossary definition (the deployable unit that bundles skills, +agents, hooks, and MCP servers into a single installable directory) and mirroring the existing +`plugins/git/` plugin's shape. The old flat skill stays at `plugins/bin/skills/gitea/` untouched +for now, kept as a reference/fallback — not deleted in this pass; removal is a future cleanup once +the new structure is validated in practice. + +**Scope expansion.** Coverage expands beyond the original 5 domains to 3 new domains verified +working with the current token scope (`write:issue`, `write:repository`) per +`plugins/bin/skills/gitea/references/token-access.md`: Files (get/create/update/delete file, dir +contents, repo tree), Commits (list/get), and Releases & Tags (full CRUD). Domains not added: +repo/org listing, user identity, notifications, and packages are blocked by token scope +(`read:user`, `read:organization`, `read:notification`, `read:package`); Actions/CI (list_runs and +secrets return 403, writes untested) and Wiki (404 on this repo, writes untested) are partially +broken or unverified. All are deferred to future issues once scope is expanded or the domain is +verified safe elsewhere. + +**Domain skill split.** The flat skill becomes 6 domain skills plus a workflow orchestrator and an +agent counterpart, composed per the Skill composition pattern: + +- `gitea-issues` — issues only (list/read/write/search); closes out 4 enrichments deferred from + issue #6 comment #848 — milestone assignment on create, assignee on create (documented + workaround since `get_me`/`read:user` is blocked), dependency-linking convention ("Depends on + #N" in body, since gitea-mcp has no native dependency field) — and delegates label inference to + `gitea-labels-milestones`. +- `gitea-labels-milestones` — split out as its own shared skill since labels/milestones are + cross-cutting (apply to both issues and PRs), rather than bundled under `gitea-issues`; owns the + label inference guide (context-pattern → Kind/*/Priority/*/Status/* taxonomy mapping). +- `gitea-prs` — pull requests + reviews, composes `gitea-labels-milestones` for label/milestone + application. +- `gitea-branches` — branches + commits bundled together (commits are read-only history within + branches, a natural pairing). +- `gitea-files` — new domain. +- `gitea-releases` — releases + tags bundled together. +- `gitea-workflow` — thin human-facing orchestrator mirroring `git-workflow` + (`plugins/git/skills/git-workflow/`). Preserves the original flat skill's default no-args status + view (composes `gitea-issues` + `gitea-prs`) and routes ambiguous requests to the right domain + skill. Named `gitea-workflow`, not bare `gitea`, for naming consistency with the other 6 skills, + despite breaking the old `/gitea` invocation muscle memory — an explicit accepted tradeoff. +- `gitea-orchestrate` (agent, not skill) — agent-facing deterministic counterpart mirroring + `git-orchestrate`, for multi-step composition when the caller is an agent rather than a human. + +**Reference-file signature sourcing.** Each new skill's `references/*.md` restates verified MCP +call signatures cross-checked live via `ToolSearch` at authoring time, not copied from +`api-reference.md`, which could drift from the deployed MCP server version. This resolves issue #6 +comment #849's root-cause question about the original `type` parameter bug, which happened +because the skill was authored from Gitea REST API docs instead of the actual MCP tool schema. +This is applied manually during this authoring pass; the `kyberforge:skill-author` meta-skill +itself is not changed — comment #849's "option 2" process fix is considered and explicitly +deferred as out of scope for this PR. + +**MCP config deferred.** `plugins/gitea/.mcp.json` is deliberately left as an empty `mcpServers` +block — the real gitea-mcp server config continues to live in the user's `~/.claude.json` rather +than being wired into the plugin manifest. This means the gitea plugin is not yet installable +standalone via `claude plugin install gitea@holocron` without manual MCP setup. A follow-up Gitea +issue tracks closing this gap. + +**Research backfill.** The existing research docs +(`plugins/gitea/docs/research/docs/gitea/`) are 100% code-derived from gitea-mcp source with zero +external/best-practice content (the original docs.gitea.com fetch timed out and was never +retried). Context7 has `/websites/gitea` (official docs mirror) and `/git_gitea_com/gitea_tea` (Tea +CLI) available now — backfilled via a parallel research pass before skill-authoring, so the +Provenance chain (`source_keys` → `sources.md` → research doc) has real external sources for +workflow/convention guidance, not just API mechanics. + +**Authoring route.** All 8 artifacts (7 skills + 1 agent) are authored via `kyberforge:forge`, not +direct `skill-author`/`agent-author` calls, even though `forge`'s own routing rule would normally +bypass itself here since the target artifact types are already known — chosen deliberately for +uniform audit/recheck coverage across every artifact. + +## Considered options + +**5-skill split, labels+milestones bundled under `gitea-issues` (rejected)** — simpler, one fewer +skill, but re-buries label/milestone logic inside an issues-specific skill even though PRs need it +equally, forcing `gitea-prs` to either duplicate the guide or reach into `gitea-issues`' +`references/` — breaking the self-contained skill boundary. + +**8-skill split, one skill per raw API domain, no bundling (rejected)** — e.g. separate +`gitea-commits` and `gitea-tags` skills. Rejected as over-fragmentation: commits are read-only +history naturally scoped to branches, and tags are naturally scoped to releases, so bundling +avoids two near-empty skills each routing to a single tool family. + +## Consequences + +- `plugins/gitea/` gains `skills/gitea-issues/`, `skills/gitea-labels-milestones/`, + `skills/gitea-prs/`, `skills/gitea-branches/`, `skills/gitea-files/`, `skills/gitea-releases/`, + `skills/gitea-workflow/`, and `agents/gitea-orchestrate.md` (+ Copilot counterpart), each with + its own `references/` and provenance records. +- `plugins/gitea/.mcp.json` stays an empty `mcpServers` block until the follow-up issue wires in + the real gitea-mcp server config; the plugin is not standalone-installable until then. +- `plugins/bin/skills/gitea/` remains in place, unreferenced by new work, until a future cleanup + issue removes it once the new structure is validated in practice. +- Follow-up issues are needed for: the deferred domains (Actions/CI, Wiki, Notifications, + Packages, User/Org), the `.mcp.json` wiring gap, and the eventual removal of + `plugins/bin/skills/gitea/`. +- Future domain-plugin work in this repo can point to this ADR as the template for splitting an + MCP-wrapping skill into deep modules. diff --git a/plugins/gitea/.claude-plugin/plugin.json b/plugins/gitea/.claude-plugin/plugin.json index 68b602c..a805fdf 100644 --- a/plugins/gitea/.claude-plugin/plugin.json +++ b/plugins/gitea/.claude-plugin/plugin.json @@ -10,9 +10,10 @@ "issues", "prs", "milestones", - "releases" + "releases", + "branches" ], "license": "MIT", "name": "gitea", - "version": "1.0.0" + "version": "1.3.1" } diff --git a/plugins/gitea/agents/gitea-orchestrate.agent.md b/plugins/gitea/agents/gitea-orchestrate.agent.md new file mode 100644 index 0000000..2f97749 --- /dev/null +++ b/plugins/gitea/agents/gitea-orchestrate.agent.md @@ -0,0 +1,79 @@ +--- +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. + +tools: ["execute", "read"] + +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go + - context7-websites-gitea + - context7-gitea-tea-cli + +--- + +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 resolve `owner`/`repo` once per session (via `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. +- 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`. + +### 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 (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, 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 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 `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 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 +9. Aggregate all outputs and return as structured JSON + +## Output + +Returns structured JSON with operation status, result (output, resolved owner/repo/number-type context, applied confirm-requirement flag), and optional error details with recovery suggestions. diff --git a/plugins/gitea/agents/gitea-orchestrate.md b/plugins/gitea/agents/gitea-orchestrate.md new file mode 100644 index 0000000..bebf398 --- /dev/null +++ b/plugins/gitea/agents/gitea-orchestrate.md @@ -0,0 +1,95 @@ +--- +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. + +tools: Bash, Read + +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go + - context7-websites-gitea + - context7-gitea-tea-cli + +--- + +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 resolve `owner`/`repo` once per session (via `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. +- 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`. + +### 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 (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, 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 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 `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 +9. Aggregate all outputs and return as structured JSON + +## Output + +```json +{ + "status": "success" | "error", + "operation": "", + "result": { + "output": "", + "context": { "owner": "...", "repo": "...", "resolved_number_type": "issue" | "pull" | null }, + "applied_config": { "confirm_required": true | false } + }, + "error": { + "message": "", + "code": "", + "recovery_attempted": true | false, + "suggestions": ["", ""] + } +} +``` diff --git a/plugins/gitea/docs/research/docs/gitea/sources.md b/plugins/gitea/docs/research/docs/gitea/sources.md index f2600e5..aee54f1 100644 --- a/plugins/gitea/docs/research/docs/gitea/sources.md +++ b/plugins/gitea/docs/research/docs/gitea/sources.md @@ -20,3 +20,17 @@ - **Description:** Gitea REST API swagger documentation covering underlying endpoints for issues, PRs, labels, milestones, and branches - **Contributing files:** (see notes below) - **Status:** `no content extracted` — source fetch timed out; all reference content derived from gitea-mcp source files which are authoritative for MCP tool usage + +## context7-websites-gitea + +- **URL:** context7:/websites/gitea +- **Description:** Official Gitea docs mirror on Context7 (docs.gitea.com content) — scoped/exclusive label conventions, branch protection and PR review/merge rules, release and webhook semantics, issue/PR automatic cross-reference linking, protected-branch signed-commit safeguards, reverse-proxy request size limits, and repository upload limits. Backfills the external/best-practice gap left by the original docs.gitea.com fetch timeout. +- **Contributing files:** workflow-conventions.md, troubleshooting.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, including semver tag/release conventions, draft/prerelease flags, and release-notes-from-file conventions. +- **Contributing files:** workflow-conventions.md +- **Status:** `extracted` diff --git a/plugins/gitea/docs/research/docs/gitea/troubleshooting.md b/plugins/gitea/docs/research/docs/gitea/troubleshooting.md index 5ad1632..fb94da9 100644 --- a/plugins/gitea/docs/research/docs/gitea/troubleshooting.md +++ b/plugins/gitea/docs/research/docs/gitea/troubleshooting.md @@ -3,6 +3,7 @@ topic: troubleshooting source_keys: - gitea-mcp-repo - gitea-mcp-slim-go + - context7-websites-gitea --- # Gitea MCP — Troubleshooting @@ -86,6 +87,14 @@ The MCP server surfaces HTTP error codes from the Gitea REST API: The `milestones` parameter on `list_issues` accepts milestone names or IDs as an array. Using IDs is more reliable — milestone names are mutable. Always prefer filtering by milestone ID when programmatically filtering. +## Signed-commit branch protection can silently block file writes + +A protected branch can require signed commits as a safeguard (`docs.gitea.com/usage/access-control/protected-branches`). `create_or_update_file` and `delete_file` both create commits server-side via a bare API token call with no 2FA/PGP context — if the target branch's protection rule requires signed commits, Gitea rejects the push outright. The MCP tool surfaces this as a generic write failure (typically 403 or 422), not a message naming "signed commit required" — `get_file_contents` (read) keeps succeeding right up until the write. When a file write fails without a 409 (missing/stale SHA) or 404 (bad path) explanation, check whether the target branch's protection rule requires signed commits before assuming the SHA is wrong. + +## Large file content can hit a reverse-proxy 413, not a Gitea limit + +`create_or_update_file` sends `content` base64-encoded, which inflates the payload ~33% over the raw file size. A `413 Request Entity Too Large` response is commonly a reverse-proxy body-size limit in front of the Gitea instance (e.g. nginx `client_max_body_size`), not a Gitea-side rejection (`docs.gitea.com/administration/reverse-proxies`, `docs.gitea.com/help/faq`) — Gitea's own configured upload limit is a separate, unrelated setting (50MB per file by default, for the web upload feature). A 413 on a file-write call is an infrastructure-layer symptom that cannot be fixed by changing the request (a different SHA, path, or branch won't help) — it requires the reverse-proxy config to be raised, which is outside the skill's or the calling agent's control. Surface this distinction to the user rather than retrying the same call. + ## `per_page` defaults vary Not all endpoints share the same default `per_page`: diff --git a/plugins/gitea/docs/research/docs/gitea/workflow-conventions.md b/plugins/gitea/docs/research/docs/gitea/workflow-conventions.md new file mode 100644 index 0000000..c27a680 --- /dev/null +++ b/plugins/gitea/docs/research/docs/gitea/workflow-conventions.md @@ -0,0 +1,88 @@ +--- +topic: workflow-conventions +source_keys: + - context7-websites-gitea + - context7-gitea-tea-cli +--- + +# Gitea Workflow Conventions + +Practitioner conventions and platform behavior that inform *how* to use the mechanics already +documented in `api-reference.md` — not additional tool schemas. + +## Scoped, exclusive labels + +Gitea labels support a scoped-label convention: a `/` delimiter in the label name (e.g. +`Priority/High`) plus an `exclusive: true` flag means only one label from that scope can be applied +to an issue or PR at a time — applying a new `Priority/*` label automatically replaces the previous +one. This is exactly the `Kind/*` / `Priority/*` / `Status/*` taxonomy already used in this repo's +own label set, confirming the taxonomy follows Gitea's native scoped-label convention rather than an +ad hoc naming scheme. When defining or inferring labels, a scope prefix implies mutual exclusivity — +label inference logic should replace, not add to, existing labels in the same scope. + +Repositories can also seed a predefined label set at creation time from a YAML label file +(`name`, `color`, `description`, `exclusive`), which is where the base `Kind/Priority/Status` sets +typically originate. + +## Milestone and label state as first-class transitions + +Both issues and PRs treat labeling and milestoning as discrete state-transition events +(`label_updated`/`label_cleared`, `milestoned`/`demilestoned`), not passive metadata fields. This +reinforces treating `gitea-labels-milestones` as a shared skill: the same transition semantics apply +whether the target is an issue or a PR. + +## Automatic cross-reference linking + +Gitea auto-renders issue/PR references in body text without any API call: `#1234` and `!1234` both +resolve to issue/PR 1234 in the same repo (issues and PRs share one number space, consistent with +`data-model.md`); cross-repo references use `owner/repo#1234` (issue) or `owner/repo!1234` (PR). This +directly validates the dependency-linking convention decided for `gitea-issues` (issue #6, comment +#848): writing `Depends on #N` in an issue body is not just a text convention — Gitea renders it as +a real clickable cross-reference automatically, with no separate API call required. For +external-issue-tracker repos, the same syntax renders as an external link instead, so the skill +should assume same-repo internal linking unless told otherwise. + +## Pull request review workflow + +The reviewer flow is: comment, request changes, or approve; the author pushes updates to the same +branch, which the PR auto-tracks; maintainers merge once approved. Protected branches can layer +additional constraints on top of this base flow: + +- An allowlist of users/teams may be required to approve before merge is possible. +- A minimum approval count can be enforced. +- Stale approvals (approvals given before new commits were pushed) can be auto-dismissed or ignored. +- Merge can be blocked if any review requests changes, if requested reviewers haven't reviewed yet, + or if the branch is outdated relative to its base. +- Repository admins are not exempt from these rules by default — an explicit + "administrators must follow branch protection rules" setting is what removes their force-merge + bypass. + +A skill that merges PRs should treat "CI passing" and "reviews satisfied" as two independently +checkable gates — `get_status` covers CI, but review/approval state and branch-protection +constraints are a separate check the merge call itself will enforce server-side and return as an +error if unmet. + +## Release and tag conventions + +Releases are conceptually separate from tags but always tied to one: a release wraps a tag with a +title, notes, and optional binary assets. Practitioner convention (per the `tea` CLI, the reference +Gitea client) is: + +- Tag names are semver-style, typically `v`-prefixed (`v1.2.0`, `v2.0.0-beta.1`). +- Release notes are commonly sourced from a changelog file rather than typed inline. +- Draft and prerelease are separate boolean flags, not states inferred from the tag name — a + prerelease is anything with a `-beta`/`-rc` style suffix by convention, but Gitea does not enforce + this; the skill should treat `draft`/`prerelease` as flags the caller sets explicitly rather than + something to infer from the tag string. +- Deleting a release does not delete its tag by default — the two are separate destructive + operations (confirmed by `delete_release` taking a numeric release ID per `troubleshooting.md`, + distinct from `delete_tag`). + +## File editing: direct commit vs. PR + +Gitea's own UI defaults to prompting for a target branch when creating/editing a file directly +through the web interface, and supports pre-filling a new file's path and content via query +parameters — reflecting that direct-commit file edits are a first-class, expected workflow (not just +an API escape hatch). This supports `create_or_update_file`/`delete_file` being used directly against +a working branch as a normal editing action, with the SHA-currency requirement (`troubleshooting.md`) +being the main gotcha rather than direct-commit being an anti-pattern to avoid. diff --git a/plugins/gitea/plugin.json b/plugins/gitea/plugin.json index 18768f1..89fed4c 100644 --- a/plugins/gitea/plugin.json +++ b/plugins/gitea/plugin.json @@ -1,4 +1,5 @@ { + "agents": "agents/", "author": { "email": "defame1297@rkdr.net", "name": "Defame1297" @@ -10,10 +11,14 @@ "issues", "prs", "milestones", - "releases" + "releases", + "branches" ], "license": "MIT", "mcpServers": ".mcp.json", "name": "gitea", - "version": "1.0.0" + "skills": [ + "skills/" + ], + "version": "1.3.1" } diff --git a/plugins/gitea/skills/gitea-branches/README.md b/plugins/gitea/skills/gitea-branches/README.md new file mode 100644 index 0000000..1be8bcc --- /dev/null +++ b/plugins/gitea/skills/gitea-branches/README.md @@ -0,0 +1,37 @@ +# gitea-branches + +Manage Gitea repository branches and inspect commit history via the Gitea MCP server. + +## What it does + +This skill handles branch lifecycle operations (list, create, delete) and read-only commit +history (list commits, get a single commit's full detail) against a Gitea repository. It resolves +`owner`/`repo` from the git remote, dispatches to the right MCP tool, and applies safety and +pagination conventions specific to Gitea's API (e.g. refusing to delete a protected branch without +explicit confirmation, and treating unexpected 404s as possible masked 403s). + +## Before you start + +Requires a Gitea MCP server configured with a token that has `write:repository` scope. This is +confirmed for `list_branches`, `create_branch`, and `delete_branch` (Gitea gates reads behind write +scope for repo-scoped operations); `list_commits` and `get_commit` are inferred to need the same +scope by analogy, not explicitly confirmed — see `references/commits.md`. Requires a git remote +named `origin` pointing at the Gitea instance. + +## Usage + +``` +/gitea-branches +``` + +Describe your task: list/create/delete a branch, or list/inspect commits. See `SKILL.md`'s +dispatch table for the full set of recognized invocations. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents — dispatch table, gotchas | +| `references/branches.md` | Verified call signatures and mechanics for list/create/delete branch | +| `references/commits.md` | Verified call signatures and mechanics for list/get commit | +| `references/sources.md` | Research sources backing the branch/commit guidance | diff --git a/plugins/gitea/skills/gitea-branches/SKILL.md b/plugins/gitea/skills/gitea-branches/SKILL.md new file mode 100644 index 0000000..654ebfc --- /dev/null +++ b/plugins/gitea/skills/gitea-branches/SKILL.md @@ -0,0 +1,66 @@ +--- +name: gitea-branches + +description: > + Use when managing Gitea repository branches — listing, creating, or deleting + branches — or inspecting commit history within a Gitea repo: listing commits + (optionally filtered by branch or file path) or getting full detail for a + single commit by SHA. Triggers on "list branches", "create a branch", + "delete a branch", "what commits are on this branch", "show commit ", + "what changed in that commit" — even if the user doesn't say "Gitea" + explicitly, as long as the repo's remote is a Gitea instance. Do not use for + local git branch/commit operations on your working copy (use git-branches or + git-history) or for PR-side branch references like cross-repo fork PR heads + (use gitea-prs). + +compatibility: Requires Gitea MCP server configured with a token with write:repository scope; this is confirmed to gate list_branches, create_branch, and delete_branch (Gitea gates reads behind write scope for repo-scoped operations), and is inferred by analogy (not explicitly confirmed by source docs) to also gate list_commits and get_commit. Requires git remote "origin" pointing to the Gitea instance. + +metadata: + category: integration + version: "0.1.1" + 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__delete_branch mcp__gitea__list_commits mcp__gitea__get_commit +--- + +## Gotchas + +- **Never delete a protected branch (`main`/`master` by name, or `protected: true` from `list_branches`) without explicit confirmation.** `delete_branch` is a direct API call, not a local `git push` — there is no client-side force-push guard protecting it. Name-matching `main`/`master` is a convenient default but not authoritative — a repo can protect a differently-named default branch. When in doubt, call `list_branches` first and check `protected` on the target; treat deletion of any protected branch as a hard refusal unless the user explicitly confirms in the conversation. +- **404 may actually mean 403.** Gitea hides permission errors as not-found to avoid leaking resource existence. If any of these five tools returns 404 unexpectedly, check token scope (see `references/branches.md` / `references/commits.md`) before concluding the branch or commit doesn't exist. +- **Pagination is manual.** `list_branches` and `list_commits` return one page at a time — no auto-pagination in the MCP layer. When you need a complete list, iterate `page: 1, 2, ...` until the returned count is less than `per_page`. +- **Owner/repo always come from the git remote, never from `get_me`.** Resolve them via `git remote get-url origin` (Step 1 below). `get_me`/`list_my_repos` are blocked under the token scopes this skill assumes. +- **`create_branch`'s source is `old_branch`, not "wherever gitea-mcp feels like."** Omitting `old_branch` forks from the repo's server-side default branch — not necessarily the branch you're currently working on locally. If you want to branch from your current checkout, pass `old_branch` explicitly. + +## Step 1 — Resolve owner and repo + +Before any tool call, extract `owner` and `repo` from the git remote: + +```bash +git remote get-url origin +``` + +If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." + +## Step 2 — Dispatch + +| Invocation | Action | +|---|---| +| `/gitea-branches` or `/gitea-branches list` | List branches | +| `/gitea-branches create [from ]` | Create branch | +| `/gitea-branches delete ` | Delete branch | +| `/gitea-branches commits [on ] [touching ]` | List commit history | +| `/gitea-branches commit ` | Get full detail for one commit | + +For branch operations (list/create/delete), read `references/branches.md`. +For 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/delete): confirm the action taken, the branch name, and (for create) the base it forked from. + +For errors: surface the HTTP code and message. If a 404 is unexpected, re-check token scope per the Gotchas above before reporting "not found" to the user. diff --git a/plugins/gitea/skills/gitea-branches/references/branches.md b/plugins/gitea/skills/gitea-branches/references/branches.md new file mode 100644 index 0000000..b94754d --- /dev/null +++ b/plugins/gitea/skills/gitea-branches/references/branches.md @@ -0,0 +1,81 @@ +--- +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` +at authoring time, 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. Re-verify +against the live schema if these tools appear to 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:** +``` +list_branches owner: repo: +``` + +**Response:** one object per branch: `name`, `protected` (bool), `commit_sha` (present when the +underlying commit data is available). + +Paginate if you need the full list (see Gotchas in SKILL.md) — iterate `page` until the returned +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:** +``` +create_branch owner: repo: branch: old_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 +(`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`. + +## `delete_branch` + +**Parameters:** +- `owner` (string, required) +- `repo` (string, required) +- `branch` (string, required) + +**Call:** +``` +delete_branch owner: repo: branch: +``` + +Before calling this, see the hard-refusal 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 + +All three — `list_branches`, `create_branch`, `delete_branch` — 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. diff --git a/plugins/gitea/skills/gitea-branches/references/commits.md b/plugins/gitea/skills/gitea-branches/references/commits.md new file mode 100644 index 0000000..fddeff5 --- /dev/null +++ b/plugins/gitea/skills/gitea-branches/references/commits.md @@ -0,0 +1,73 @@ +--- +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` at authoring time, +not copied from research docs, for the same drift-avoidance reason noted in `references/branches.md`. + +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:** +``` +list_commits owner: repo: sha: path: +``` + +Dispatch defaults: +- "commits on ``" → pass `` as `sha`. +- "commits touching ``" (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 manual-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:** +``` +get_commit owner: repo: 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 in `overview.md` (Gitea gates reads behind write scope for +repo-scoped operations), not a claim `overview.md` makes for commits by name: its explicit +`write:repository` enumeration lists PR, branch, file, release, and tag operations, but doesn't +mention commits. 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`). diff --git a/plugins/gitea/skills/gitea-branches/references/sources.md b/plugins/gitea/skills/gitea-branches/references/sources.md new file mode 100644 index 0000000..0341ef5 --- /dev/null +++ b/plugins/gitea/skills/gitea-branches/references/sources.md @@ -0,0 +1,41 @@ +# 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 at authoring time — they are not 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 (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags. 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; 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` diff --git a/plugins/gitea/skills/gitea-files/README.md b/plugins/gitea/skills/gitea-files/README.md new file mode 100644 index 0000000..52f560d --- /dev/null +++ b/plugins/gitea/skills/gitea-files/README.md @@ -0,0 +1,23 @@ +# gitea-files + +Read and write individual files and directory/repository trees in a Gitea repository via the Gitea MCP server. + +## What it does + +This skill handles file-domain operations within the Gitea integration suite: reading a single file's contents, listing one directory level, walking a full repository tree (optionally recursive), creating or updating a file, and deleting a file. It owns the SHA-based optimistic-concurrency pattern that Gitea requires for file writes — the domain's sharpest gotcha — and defers branch creation, commit history, and pull request mechanics to `gitea-branches` and `gitea-prs`. + +## Usage + +``` +/gitea-files +``` + +Describe the file task: read a file or directory, walk a tree, create/update a file, or delete a file. Provide `owner`/`repo`/branch (or ask the user if not given) — this skill does not resolve them from a git remote itself. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents | +| `references/examples.md` | Canonical call sequences: branch + file + PR, recovering a missing SHA before an update, deleting a file | +| `references/sources.md` | Research sources backing the SHA/concurrency and direct-commit-vs-PR guidance | diff --git a/plugins/gitea/skills/gitea-files/SKILL.md b/plugins/gitea/skills/gitea-files/SKILL.md new file mode 100644 index 0000000..f687f70 --- /dev/null +++ b/plugins/gitea/skills/gitea-files/SKILL.md @@ -0,0 +1,56 @@ +--- +name: gitea-files + +description: > + Use when reading or writing individual files or directory trees in a Gitea repository via the + Gitea MCP server: reading a file's contents, listing a directory, walking a full repository + tree, creating a new file, updating an existing file, or deleting a file. Triggers on "read this + file from the repo", "what's in this directory", "show me the repo tree", "create/update a file + in Gitea", "commit this file to the branch", "delete this file from the repo" — even when the + user doesn't say "Gitea" explicitly, as long as the target is a Gitea-hosted repository. Do not + use for local filesystem file operations (use Read/Write/Edit), for branch or commit history + (use gitea-branches), or for opening a pull request around a file change (use gitea-prs after + the file write completes here). + +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: + 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 + +- **A 404 from any read call may actually be a 403 in disguise.** `get_file_contents`, `get_dir_contents`, and `get_repository_tree` all gate on `write:repository` scope, not just read access — some Gitea endpoints return 404 instead of 403 when the token's scope is insufficient, to avoid leaking whether the resource exists. If a read fails with 404 on a path you're confident is correct, check the token's configured scopes before concluding the file or directory doesn't exist. +- **SHA is the concurrency token for every write — and it lives at the top level of `get_file_contents`'s response, not nested under `content`.** `create_or_update_file` without `sha` is always treated as a *create*: if the path already exists, Gitea returns HTTP 409. `delete_file` has no optional path at all — omitting `sha` returns HTTP 422. The safe sequence for any update or delete is always: call `get_file_contents` first, read the top-level `sha` field, then pass that exact value to the write call. Never guess or reuse a stale SHA — a mismatched SHA is rejected the same as a missing one. +- **A write can also fail because the branch requires signed commits — a separate failure mode from a bad SHA.** `create_or_update_file` and `delete_file` create commits server-side via a bare API token call with no 2FA/PGP context. If the target branch's protection rule requires signed commits, Gitea rejects the write outright — surfaced as a generic 403 or 422, not an error naming "signed commit required," and reads against that same branch keep succeeding right up until you try to write. When a write fails without a clean 409 (missing/stale SHA) or 404 (bad path) explanation, check whether the branch's protection rule requires signed commits before assuming the SHA is wrong and retrying. +- **A large `create_or_update_file` payload can hit a reverse-proxy 413 that has nothing to do with Gitea.** `content` is base64-encoded, which inflates the payload ~33% over the raw file size; a 413 is commonly a reverse-proxy body-size limit in front of the Gitea instance, not a Gitea-side rejection. No amount of retrying, or changing the SHA, path, or branch, will fix it — it needs the proxy's config raised, which is outside this skill's or the calling agent's control. Surface that distinction to the user instead of retrying the same call. +- **`get_dir_contents` and `get_repository_tree` are not SHA sources for a specific file's write.** `get_dir_contents` entries carry no `sha` at all. `get_repository_tree` entries do carry a `sha` (a blob/tree hash), but fetching it means an extra round trip with no content — `get_file_contents` is the canonical path since it returns the decoded content and the write-ready `sha` in one call. +- **`owner` and `repo` are always caller-supplied inputs, never resolved here.** This skill doesn't infer them from a git remote. If invoked directly by a human, ask for them if not stated. If invoked by `gitea-workflow` or an orchestrating agent, expect them to already be resolved and passed in. +- **Direct commits to a branch are a first-class action, not a workaround.** Gitea's own web UI defaults to editing files directly against a branch — `create_or_update_file`/`delete_file` used that way is normal, not an API escape hatch to avoid. The SHA-currency requirement above is the actual risk to manage, not the act of committing directly. +- **`ref` (reads) vs. `branch_name` (writes) are different parameters for the same concept.** `get_file_contents`, `get_dir_contents`, and `get_repository_tree` (as `tree_sha`) all accept a branch name, tag, or commit SHA to select what to read. `create_or_update_file` and `delete_file` instead take `branch_name` — the branch the commit lands on. Don't conflate the two when chaining a read into a write. +- **Content is base64.** `create_or_update_file`'s `content` parameter is base64-encoded file content, not raw text — encode before calling. `get_file_contents`'s response content is likewise base64-encoded (decode after reading), unless `withLines: true` is passed for a numbered-line view. + +## Reading + +- **Single file:** `get_file_contents(owner, repo, ref, path)`. Pass `withLines: true` only when you need line numbers for referencing specific lines (e.g. quoting a snippet back to the user); omit it for a normal content fetch. +- **One directory level:** `get_dir_contents(owner, repo, ref, path)` — returns immediate entries only (name, path, type, size), no recursion, no SHA, no content. +- **Whole tree:** `get_repository_tree(owner, repo, tree_sha, recursive)` — `tree_sha` accepts a SHA, branch, or tag name despite the name. Set `recursive: true` to walk subdirectories in one call. Response includes `truncated: true` when a page doesn't hold every entry — page through with `page`/`per_page` (default `page: 1`, `per_page: 30`) until you get fewer results than `per_page`. + +## Writing + +- **Creating a new file:** call `create_or_update_file(owner, repo, path, content, message, branch_name)` with `sha` omitted entirely. +- **Updating an existing file:** call `get_file_contents(owner, repo, ref: branch_name, path)` first, take the top-level `sha`, then call `create_or_update_file(..., sha: )`. +- **Deleting a file:** call `get_file_contents` first the same way, then `delete_file(owner, repo, path, message, branch_name, sha: )` — `sha` is required, no create-style fallback exists. +- **Creating a new branch as part of the write:** pass `new_branch_name` on `create_or_update_file` to branch off before the commit lands, instead of calling a separate branch-creation step. + +If the change needs review before merging, or targets a protected branch, hand off to `gitea-prs` after the write lands here to open the pull request — this skill's scope ends at the commit. + +If you need the full multi-call sequence rather than the single-call summary above — e.g. branching off as part of a file push ahead of opening a PR, or recovering a SHA you didn't capture earlier — read `references/examples.md`. diff --git a/plugins/gitea/skills/gitea-files/references/examples.md b/plugins/gitea/skills/gitea-files/references/examples.md new file mode 100644 index 0000000..c67af6a --- /dev/null +++ b/plugins/gitea/skills/gitea-files/references/examples.md @@ -0,0 +1,65 @@ +--- +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go +--- + +# Canonical call sequences + +## Push a file to a new branch, then open a PR + +``` +1. get_repository_tree or get_file_contents on the base branch — only needed if the + new file is actually replacing an existing one; skip for a brand-new path. + +2. create_or_update_file + owner, repo + path: "docs/example.md" + content: "" + message: "docs: add example" + branch_name: "main" + new_branch_name: "feat/add-example" ← branches off before the commit lands + (sha omitted — this is a new file) + +3. Hand off to gitea-prs to open a PR from "feat/add-example" into "main". +``` + +`new_branch_name` on `create_or_update_file` replaces a separate branch-creation call — the branch is created and the commit lands on it in one step. + +## Update a file when you don't already have its SHA + +SHA is mandatory for updates. If it wasn't captured earlier in the conversation: + +``` +1. get_file_contents + owner, repo + ref: "main" + path: "docs/example.md" + → read the top-level `sha` field (not content.sha) + +2. create_or_update_file + owner, repo + path: "docs/example.md" + content: "" + message: "docs: update example" + branch_name: "main" + sha: "" +``` + +Do not guess or omit the SHA — the write either fails (409 on create-path fallback) or is rejected outright. + +## Delete a file + +Same SHA-first pattern, no fallback path: + +``` +1. get_file_contents owner, repo, ref: "main", path: "docs/old-example.md" + → read the top-level `sha` field + +2. delete_file + owner, repo + path: "docs/old-example.md" + message: "docs: remove old example" + branch_name: "main" + sha: "" +``` diff --git a/plugins/gitea/skills/gitea-files/references/sources.md b/plugins/gitea/skills/gitea-files/references/sources.md new file mode 100644 index 0000000..5b15165 --- /dev/null +++ b/plugins/gitea/skills/gitea-files/references/sources.md @@ -0,0 +1,46 @@ +# 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. + +**Source:** https://gitea.com/gitea/gitea-mcp + +- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md + +**Contributing files:** +- SKILL.md (Gotchas, Reading, Writing — tool parameters and SHA/concurrency behavior, cross-checked live against the deployed MCP tool schemas via ToolSearch) +- references/examples.md (canonical call sequences) + +## 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 — top-level `sha` field location, `get_dir_contents`/`get_repository_tree` not being usable SHA sources for a file write) +- references/examples.md (SHA-first update/delete sequences) + +## 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 (Gotchas — "Direct commits to a branch are a first-class action, not a workaround") + +## 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) diff --git a/plugins/gitea/skills/gitea-issues/README.md b/plugins/gitea/skills/gitea-issues/README.md new file mode 100644 index 0000000..41bb05d --- /dev/null +++ b/plugins/gitea/skills/gitea-issues/README.md @@ -0,0 +1,42 @@ +# gitea-issues + +Read and write Gitea issues — list, get, create, comment, close, and search — via the Gitea MCP server. + +## What it does + +This skill handles the issue lifecycle (`list_issues`, `issue_read`, `issue_write`, `search_issues`): +listing repo issues, reading a single issue's details/comments/labels, creating an issue, updating +its state, adding/editing comments, applying labels, and searching issues/PRs across repositories. +The create flow closes out four enrichments deferred from issue #6 comment #848: label inference +and milestone assignment (both by composing `gitea-labels-milestones`), an assignee workaround for +the blocked `get_me` scope, and the "Depends on #N" dependency-linking convention. It supersedes the +`issue`/`issue `/`issue close `/`issue comment ` dispatch in the old flat +`plugins/bin/skills/gitea/SKILL.md`, which remains in place untouched as a fallback per +`docs/adr/0011-gitea-skill-deep-modules.md`. + +## Before you start + +Requires a Gitea MCP server configured with a token holding `write:issue` + `write:repository`. +Requires a git remote named `origin` pointing at the Gitea instance, unless an orchestrating caller +(e.g. `gitea-workflow`) already resolved `owner`/`repo` for you. Label and milestone management +(creating/editing a label, creating/closing a milestone) is out of scope here — that's +`gitea-labels-milestones`, which this skill composes rather than duplicates. + +## Usage + +``` +/gitea-issues +``` + +Describe your task: list issues, create one, get/comment/close a specific issue number, or search +across repos. See `SKILL.md`'s dispatch table for the full set of recognized invocations. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents — dispatch table, Gotchas | +| `references/issues.md` | Verified call signatures and mechanics for `list_issues`/`issue_read`/`issue_write` | +| `references/search.md` | Verified call signature and mechanics for `search_issues` | +| `references/enrichments.md` | Create-flow enrichments — label inference, milestone assignment, assignee workaround, dependency-linking convention | +| `references/sources.md` | Research sources backing the issue guidance | diff --git a/plugins/gitea/skills/gitea-issues/SKILL.md b/plugins/gitea/skills/gitea-issues/SKILL.md new file mode 100644 index 0000000..7dfaab5 --- /dev/null +++ b/plugins/gitea/skills/gitea-issues/SKILL.md @@ -0,0 +1,105 @@ +--- +name: gitea-issues + +description: > + Use when reading or writing Gitea issues: listing repo issues, getting a single issue's details/ + comments/labels, creating an issue, updating its state, adding or editing comments, applying + labels via issue_write, or searching issues/PRs across repositories. Triggers on "create an + issue", "what issues are open", "get issue #N", "close issue #N", "comment on issue #N", "search + issues for X" — even when the user doesn't say "Gitea" explicitly. Composes gitea-labels- + milestones for all label inference/resolution and milestone lookup — do not use this skill to + manage label or milestone definitions themselves (create/edit/delete a label, create/close a + milestone), that's gitea-labels-milestones directly. Do not use for pull requests (use gitea-prs) + or for local git branch/commit work (use gitea-branches or git-branches). + +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.0" + 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` has no `type` or `milestones` parameter — despite `api-reference.md` documenting both.** The live MCP schema (re-verified via `ToolSearch` at authoring time — see `references/sources.md`) only accepts `owner`, `repo` (required), `state` (default `"all"`), `labels` (array of label *names*), `since`, `before` (ISO 8601), `page`, `per_page` (default 30). There is no way to filter issues-vs-PRs or by milestone through this tool. Since issues and PRs share one number space, `list_issues` results can include PR entries with no client-side filter to exclude them. If you need to know whether a specific number is a PR, call `issue_read method: "get"` and check `is_pull` — that field only appears on the single-item response, never in a list item. This exact drift (a prior skill trusted the research doc's `type` param and broke) is why this skill's reference files were re-verified live rather than copied from `api-reference.md`. +- **`search_issues` does have a working `type` filter** (`"issues"` | `"pulls"`) — unlike `list_issues`. Its `labels` parameter is also shaped differently: a comma-separated string, not an array of names. +- **Labels are numeric IDs on write, name strings on read.** `issue_write`'s `labels` parameter (used by `add_labels`/`replace_labels`) takes IDs. `list_issues`/`issue_read` return names. Never resolve this yourself — compose `gitea-labels-milestones` (see `references/enrichments.md`) to get IDs. +- **Milestone on `issue_read` is `{id, title}`** — an object, not a bare string. This skill only ever needs the `id`. (The bare-title-string case only happens on the PR side, which is `gitea-prs`' problem, not this skill's.) +- **Closing-keyword auto-close behavior is plausible but unconfirmed in our research docs.** Our research docs confirm Gitea does NOT auto-close an issue on a plain PR merge (unlike GitHub) — closing keywords like `Fixes #N`/`Closes #N` in a commit message are not documented one way or the other. After a PR merges (a `gitea-prs` operation), always re-check the issue's state here via `issue_read method: "get"` before deciding whether to close it manually — closing an already-closed issue is a harmless no-op, but don't assume a manual close is always needed. +- **Pagination is manual.** `list_issues` and `search_issues` return one page at a time. Iterate `page: 1, 2, ...` until the returned count is less than `per_page`. +- **HTTP 404 may actually mean 403.** Gitea hides permission errors as not-found. If a call 404s unexpectedly, verify the token holds `write:issue` scope (see `references/issues.md`'s Token scope note) before concluding the issue doesn't exist. + +## Step 1 — Resolve owner and repo + +Before any tool call, extract `owner` and `repo` from the git remote (skip this if an orchestrating caller already passed them in): + +```bash +git remote get-url origin +``` + +If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." + +## Step 2 — Dispatch + +| Invocation | Action | +|---|---| +| `/gitea-issues` or `/gitea-issues list` | List issues (optional state filter) | +| `/gitea-issues create` | Create an issue from conversation context — infers labels, checks milestone fit, applies a configured default assignee if set | +| `/gitea-issues ` | Get issue details (flag it as a PR if `is_pull: true`) | +| `/gitea-issues comments` | Get an issue's comments | +| `/gitea-issues close ` | Close an issue | +| `/gitea-issues comment ` | Add a comment from conversation context | +| `/gitea-issues search ` | Cross-repo search via `search_issues` | + +For full parameter detail on `list_issues`/`issue_read`/`issue_write`, read `references/issues.md`. For `search_issues`, read `references/search.md`. For the create-flow enrichments (label inference, milestone assignment, assignee workaround, dependency-linking), read `references/enrichments.md`. + +## Step 3 — Execute + +### list (default) + +Call `list_issues owner: repo: state: <"open"|"closed"|"all", default "all">`. Remember: results may include PR entries (see Gotchas) — if the caller needs issues only, this tool cannot filter that server-side; note the limitation rather than silently mislabeling PR entries as issues. + +### create + +1. Extract `title` and `body` from conversation context (the most recent task, bug description, or explicit statement). Fall back to an empty body if nothing is available. +2. Run the enrichment sequence in `references/enrichments.md`: infer labels (composing `gitea-labels-milestones`), check for a clearly-fitting open milestone (composing the same skill), and check for a configured default assignee. +3. Call `issue_write method: "create" owner: repo: title: body: <body> labels: [<resolved IDs, or omit>] milestone: <resolved ID, or omit> assignees: [<default login, or omit>]`. +4. Fire immediately — no confirmation step for the create itself. + +### `<N>` (get) + +Call `issue_read method: "get" owner: <owner> repo: <repo> issue_number: <N>`. If `is_pull: true`, report that this number is actually a PR and suggest `gitea-prs` for full detail. + +### `<N> comments` + +Call `issue_read method: "get_comments" owner: <owner> repo: <repo> issue_number: <N>`. + +### close `<N>` + +Call `issue_write method: "update" owner: <owner> repo: <repo> issue_number: <N> state: "closed"`. There is no `method: "close"`. + +### comment `<N>` + +Extract the comment body from conversation context (same sourcing as create). Call `issue_write method: "add_comment" owner: <owner> repo: <repo> issue_number: <N> body: <body>`. + +### search `<query>` + +Call `search_issues query: <query>`, adding `owner`, `state`, `type`, or `labels` filters if the request narrows scope (e.g. "search open PRs for X" → `type: "pulls" state: "open"`). + +## Step 4 — Report + +For reads: a compact table or numbered list — number, title, state, labels, milestone. + +For writes: confirm what was created/updated with the issue number and URL if returned. + +For errors: surface the HTTP code and message; check token scope per the Gotchas if a 404 looks wrong. diff --git a/plugins/gitea/skills/gitea-issues/references/enrichments.md b/plugins/gitea/skills/gitea-issues/references/enrichments.md new file mode 100644 index 0000000..09ae4ac --- /dev/null +++ b/plugins/gitea/skills/gitea-issues/references/enrichments.md @@ -0,0 +1,92 @@ +--- +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. This mirrors how `gitea-labels-milestones`'s +own description already frames the relationship ("`gitea-issues` and `gitea-prs` both compose it"). + +## 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: +``` +Depends on #42 +``` +to the body before calling `issue_write method: "create"`. There is no separate field or follow-up +call — the rendering happens automatically once the body is saved. diff --git a/plugins/gitea/skills/gitea-issues/references/issues.md b/plugins/gitea/skills/gitea-issues/references/issues.md new file mode 100644 index 0000000..41cc259 --- /dev/null +++ b/plugins/gitea/skills/gitea-issues/references/issues.md @@ -0,0 +1,123 @@ +--- +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` +at authoring time, 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 (see the +`list_issues` gotcha below, which is the exact drift this policy exists to catch). 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) +- `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`) + +**There is no `type` parameter and no `milestones` parameter**, despite both appearing in +`api-reference.md`. This tool cannot filter issues-vs-PRs or by milestone — see the Gotchas section +of SKILL.md for the consequence (PR entries can appear in results with no way to exclude them here). + +**Call:** +``` +list_issues owner: <owner> repo: <repo> state: "open" +``` + +**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 with `page`/`per_page` until the returned count is less than `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:** +``` +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:** +``` +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:** +``` +issue_write method: "update" owner: <owner> repo: <repo> issue_number: <N> state: "closed" +``` +There is no `method: "close"` — using one will error. + +**Comment:** +``` +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): +``` +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>`. + +## Token scope + +All of `list_issues`, `issue_read`, and `issue_write` are verified working under a token holding +`write:issue` + `write:repository`. diff --git a/plugins/gitea/skills/gitea-issues/references/search.md b/plugins/gitea/skills/gitea-issues/references/search.md new file mode 100644 index 0000000..1a449b4 --- /dev/null +++ b/plugins/gitea/skills/gitea-issues/references/search.md @@ -0,0 +1,39 @@ +--- +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) — **this tool has a working type filter**, + unlike `list_issues` (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:** +``` +search_issues query: <text> +``` + +**Narrowing the search:** +``` +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`. diff --git a/plugins/gitea/skills/gitea-issues/references/sources.md b/plugins/gitea/skills/gitea-issues/references/sources.md new file mode 100644 index 0000000..65b6516 --- /dev/null +++ b/plugins/gitea/skills/gitea-issues/references/sources.md @@ -0,0 +1,41 @@ +# 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` were re-verified live via +`ToolSearch` against the deployed `gitea-mcp` server at authoring time — they are not copied +verbatim from `api-reference.md`. This resolves issue #6 comment #849's root-cause finding that a +prior skill was authored from API docs that had drifted from the actual MCP tool schema; the live +check caught exactly this drift on `list_issues` (see SKILL.md Gotchas — the research doc documents +a `type` and a `milestones` parameter that do not exist on the deployed server). + +## 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` diff --git a/plugins/gitea/skills/gitea-labels-milestones/README.md b/plugins/gitea/skills/gitea-labels-milestones/README.md new file mode 100644 index 0000000..30fb502 --- /dev/null +++ b/plugins/gitea/skills/gitea-labels-milestones/README.md @@ -0,0 +1,25 @@ +# gitea-labels-milestones + +Read and write Gitea labels and milestones, and resolve label/milestone identity for the skills that apply them to issues and PRs. + +## What it does + +This skill handles label and milestone CRUD (`label_read`/`label_write`, `milestone_read`/`milestone_write`) — listing repo or org labels, creating/editing/deleting a label, resolving a label name to the numeric ID required for any write, and listing/creating/updating/closing/deleting a milestone. It also owns label inference: mapping conversation context (bug report, feature request, urgency language) to this repo's `Kind/*`/`Priority/*`/`Status/*` taxonomy. It is a cross-cutting shared skill composed by `gitea-issues` and `gitea-prs`, which call into it for label/milestone resolution before their own `issue_write`/`pull_request_write` calls apply the resolved IDs. + +## Usage + +``` +/gitea-labels-milestones +``` + +Describe the label or milestone task: list labels, resolve a name to an ID, create/edit/delete a label, or list/create/update/close/delete a milestone. For applying already-resolved labels or a milestone to a specific issue or PR, use `gitea-issues` or `gitea-prs` instead. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents — dispatch table and Gotchas | +| `references/labels.md` | Execution detail for `label_read`/`label_write` | +| `references/milestones.md` | Execution detail for `milestone_read`/`milestone_write` | +| `references/label-inference.md` | Context-pattern → `Kind/*`/`Priority/*`/`Status/*` label inference guide | +| `references/sources.md` | Research sources backing the label/milestone guidance | diff --git a/plugins/gitea/skills/gitea-labels-milestones/SKILL.md b/plugins/gitea/skills/gitea-labels-milestones/SKILL.md new file mode 100644 index 0000000..b9844dc --- /dev/null +++ b/plugins/gitea/skills/gitea-labels-milestones/SKILL.md @@ -0,0 +1,68 @@ +--- +name: gitea-labels-milestones + +description: > + Use when reading or writing Gitea labels or milestones — listing repo/org labels, creating, + editing, or deleting a label, resolving label names to the numeric IDs required for applying + them to an issue or PR, or listing, creating, updating, closing, or deleting a milestone. This is + a cross-cutting shared skill: `gitea-issues` and `gitea-prs` both compose it whenever they need to + apply labels or assign a milestone, rather than duplicating label/milestone logic. Also use for + label inference — mapping a bug report, feature request, or urgency signal in conversation + context to the repo's `Kind/*`/`Priority/*`/`Status/*` label taxonomy. Do not use for applying + already-resolved label IDs or milestone IDs to a specific issue or PR — that write goes through + `issue_write`/`pull_request_write` in `gitea-issues`/`gitea-prs`, not here. + +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.1" + +allowed-tools: Bash mcp__gitea__label_read mcp__gitea__label_write mcp__gitea__milestone_read mcp__gitea__milestone_write +--- + +## Gotchas + +- **Label writes take IDs, reads return names.** `label_read` is the only tool that returns full label objects (`id`, `name`, `color`, `description`). Issue/PR responses slim labels down to name strings. Before any label is applied to an issue or PR (in `gitea-issues`/`gitea-prs`), resolve names → IDs here via `label_read method: "list_repo_labels"` — never pass a name string where an ID is expected. +- **Milestones are referenced by ID everywhere, never by title.** `milestone_write` update/delete take `id`. The one place titles show up as the sole handle is the `pull_request_read` response (see next gotcha). +- **Milestone representation differs between issues and PRs.** `issue_read` returns `milestone: {id, title}` — an object. `pull_request_read` returns `milestone: "title string"` — title only, no ID. You cannot recover a milestone ID from a PR response directly; call `milestone_read method: "list"` and match by title instead. +- **Repo labels and org labels are separate pools, never mixed in one call.** `label_read`/`label_write` take either `owner`+`repo` (repo-scoped methods) or `org` (org-scoped methods) — passing both or neither for a given method is a caller error, not something the schema enforces for you. Repo and org labels can both apply to the same issue, but you list/create/edit them through different method values. +- **`milestone_write` accepts `"update"` and `"edit"` as the same operation.** Both method values map to the identical update call. Prefer `"update"` for consistency with `issue_write`/`pull_request_write`. +- **`exclusive` is documented as an org-labels-only flag — it isn't what enforces exclusivity here.** Gitea's docs scope the settable/server-enforced `exclusive` flag to org labels only, and the live `label_write` schema for `create_repo_label`/`edit_repo_label` doesn't document accepting it at all. This repo's `Kind/*`, `Priority/*`, `Status/*` groups still behave as one-label-per-scope, but that's a manually-enforced convention this skill implements client-side, not a guaranteed server behavior for repo labels: applying a new label within a scope (e.g. `Priority/High`) must replace any existing label in that same scope, not add alongside it, and nothing on the server enforces that for you. Label inference (see `references/label-inference.md`) must respect this — replace, don't stack. +- **Pagination is manual on every list call.** `label_read` and `milestone_read` both default to `per_page: 30`. Iterate `page: 1, 2, ...` until the result count is less than `per_page` — there is no cursor or auto-pagination. +- **Schema requiredness differs between the two tool families.** `milestone_read`/`milestone_write` have `owner` and `repo` as hard-required parameters (the call fails validation without them). `label_read`/`label_write` only hard-require `method` — `owner`/`repo`/`org` are functionally required per method but not schema-enforced, so passing none produces a runtime error from Gitea, not a client-side validation error. + +## Step 1 — Resolve owner and repo + +Before any tool call, extract `owner` and `repo` from the git remote (skip this if an orchestrating caller already passed them in): + +```bash +git remote get-url origin +``` + +If origin is not set or the URL is not a Gitea URL, stop and report: "No Gitea remote found — set origin to your Gitea instance URL." + +## Step 2 — Dispatch + +| Task | Tool | method | +|---|---|---| +| 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"` | + +For full parameter detail and step-by-step call sequences, read `references/labels.md` (label operations) or `references/milestones.md` (milestone operations). For mapping conversation context to a label to apply, read `references/label-inference.md`. + +Applying resolved label IDs or a milestone ID to a specific issue or PR is out of scope here — that's `issue_write`/`pull_request_write` in the composing skill (`gitea-issues`/`gitea-prs`). diff --git a/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md b/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md new file mode 100644 index 0000000..ba4665b --- /dev/null +++ b/plugins/gitea/skills/gitea-labels-milestones/references/label-inference.md @@ -0,0 +1,68 @@ +--- +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. + +## Scoped labels are mutually exclusive — replace, don't stack + +Each of `Kind/*`, `Priority/*`, `Status/*` is treated as a scoped-label group by convention (the `/` +delimiter naming pattern). Gitea's `exclusive` flag — the mechanism that would let the server itself +enforce one-label-per-scope — is documented as an org-labels-only setting, and the repo-level +`label_write` methods used here don't accept it at all. So exclusivity within these scopes is a +convention this skill enforces client-side, not something the server guarantees: applying a new +label within a scope is expected to replace any existing label in that same scope on the target +issue/PR, not add alongside it. When inference +selects a `Priority/High` label and the issue already carries `Priority/Medium`, the write should +result in only `Priority/High` remaining — use `replace_labels` scoped to that group's labels, or at +minimum remove the superseded label before adding the new one. Never leave two labels from the same +scope applied at once. + +## 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` | + +**`Priority/*`** (urgency): + +| Signal in context | Label | +|---|---| +| "blocking", "critical", "urgent", production-down | `Priority/Critical` | +| "soon", "high priority", "should do this sprint" | `Priority/High` | +| 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` | + +## 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 — inference must never guess an ID, only a name, then resolve it. +3. Match inferred label names against the resolved list (case-insensitive). If a scope group + already has a different label applied on the target and a new one is inferred for that same + scope, plan to replace rather than add (see above). +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 (plus which scopes to replace) to the caller's `issue_write`/ + `pull_request_write` call — this skill does not apply labels to an issue or PR itself. diff --git a/plugins/gitea/skills/gitea-labels-milestones/references/labels.md b/plugins/gitea/skills/gitea-labels-milestones/references/labels.md new file mode 100644 index 0000000..56b129e --- /dev/null +++ b/plugins/gitea/skills/gitea-labels-milestones/references/labels.md @@ -0,0 +1,97 @@ +--- +topic: labels +source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go +--- + +# 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 | org labels only | +| `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 + +``` +label_read method: "list_repo_labels" owner: <owner> repo: <repo> per_page: 50 +``` + +Paginate (`page: 1, 2, ...`) until the returned count is less than `per_page`. This is the only way +to build a complete name → ID map — there is no lookup-by-name endpoint. + +## Get one label + +``` +label_read method: "get_repo_label" owner: <owner> repo: <repo> id: <id> +``` + +## Resolve a name to an ID + +There is no direct name lookup. List all repo labels (paginating if needed), scan for a +case-insensitive name match, and extract `id`. This is the required first step before any label +application on an issue or PR — the actual `add_labels`/`replace_labels`/`remove_label` call lives +in `gitea-issues`/`gitea-prs` via `issue_write`/`pull_request_write`, which take numeric IDs only. + +## Create a label + +``` +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. + +## Edit a label + +``` +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 + +``` +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. diff --git a/plugins/gitea/skills/gitea-labels-milestones/references/milestones.md b/plugins/gitea/skills/gitea-labels-milestones/references/milestones.md new file mode 100644 index 0000000..d87ee72 --- /dev/null +++ b/plugins/gitea/skills/gitea-labels-milestones/references/milestones.md @@ -0,0 +1,98 @@ +--- +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 + +``` +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 + +``` +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: + +``` +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 + +``` +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 + +``` +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 + +``` +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. diff --git a/plugins/gitea/skills/gitea-labels-milestones/references/sources.md b/plugins/gitea/skills/gitea-labels-milestones/references/sources.md new file mode 100644 index 0000000..0315aac --- /dev/null +++ b/plugins/gitea/skills/gitea-labels-milestones/references/sources.md @@ -0,0 +1,33 @@ +# 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/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` diff --git a/plugins/gitea/skills/gitea-prs/README.md b/plugins/gitea/skills/gitea-prs/README.md new file mode 100644 index 0000000..3f6841e --- /dev/null +++ b/plugins/gitea/skills/gitea-prs/README.md @@ -0,0 +1,25 @@ +# gitea-prs + +List, read, create, update, merge, and review Gitea pull requests. + +## What it does + +This skill handles the pull request lifecycle within the Gitea integration suite — listing and reading PRs (details, diff, changed files, CI status, reviews), creating and updating them (title, body, reviewers, labels, milestone), closing/reopening, merging with a chosen strategy and post-merge branch cleanup, and the full code-review flow (create a review with inline comments, submit it, dismiss or delete it). It composes `gitea-labels-milestones` for label/milestone ID resolution rather than duplicating that logic, and defers to `gitea-issues` for anything that turns out to be an issue rather than a PR (they share one number space) and to `gitea-branches`/`gitea-files` for the underlying branch/file operations behind a PR. + +## Usage + +``` +/gitea-prs +``` + +Describe the PR or review task: list PRs, get a PR's status/diff/reviews, create or update a PR, merge one, or create/submit/dismiss a code review. The skill will determine owner/repo from context and resolve any label or milestone names via `gitea-labels-milestones` before writing them. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents — Gotchas, composition with `gitea-labels-milestones`, and the dispatch table | +| `references/pull-requests.md` | Execution detail for `list_pull_requests`, `pull_request_read` (get/get_diff/get_files/get_status), and `pull_request_write` (create/update/close/reopen/update_branch/add_reviewers/remove_reviewers) | +| `references/reviews.md` | Execution detail for `pull_request_review_write` (create/submit/delete/dismiss) and the review-related `pull_request_read` methods | +| `references/merging.md` | The merge workflow — CI vs. review/branch-protection gates, merge styles, branch cleanup, and the post-merge issue-close check | +| `references/sources.md` | Research sources backing the PR/review guidance | diff --git a/plugins/gitea/skills/gitea-prs/SKILL.md b/plugins/gitea/skills/gitea-prs/SKILL.md new file mode 100644 index 0000000..ee2c449 --- /dev/null +++ b/plugins/gitea/skills/gitea-prs/SKILL.md @@ -0,0 +1,68 @@ +--- +name: gitea-prs + +description: > + Use when listing, reading, creating, updating, merging, or reviewing Gitea pull requests — + getting PR status/diff/changed files/CI status, opening a PR, updating title/body/reviewers, + closing/reopening, merging with a chosen strategy, or submitting/dismissing a code review with + inline comments. Composes `gitea-labels-milestones` to resolve label names or milestone titles + to the numeric IDs `pull_request_write` requires, rather than duplicating that resolution logic. + Do not use for issues (`gitea-issues`) or branch/commit operations (`gitea-branches`) — a number + the user mentions may refer to either an issue or a PR since they share one number space, so + confirm which domain applies before dispatching. + +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.1" + +allowed-tools: mcp__gitea__list_pull_requests mcp__gitea__pull_request_read mcp__gitea__pull_request_write mcp__gitea__pull_request_review_write +--- + +## Gotchas + +- **Issues and PRs share one number space.** A number the user mentions (`#42`) might be an issue, not a PR — there is only one counter per repo. If you're not certain, call `pull_request_read method: "get"` and treat a 404 as "this number is an issue, not a PR" (or check `is_pull` on an `issue_read` response first if you already have one). +- **`pull_request_read method: "get"` returns `review_scomments`, not `review_comments`.** Source-level typo in gitea-mcp v1.3.0. Never reference `review_comments` — it will always be undefined. +- **`draft: true` on create prepends `"WIP:"` to the title.** There is no first-class draft field — Gitea implements draft PRs via title prefix. To un-draft, call `update` and pass the title without the `WIP:` prefix. +- **Cross-repo fork PRs require `head` as `"fork-owner:branch-name"`.** A bare branch name causes Gitea to search the base repo for it and return 422. Same-repo PRs use a bare branch name. +- **PR `milestone` is a bare title string, not `{id, title}`.** Unlike issues, you cannot recover a milestone's ID from a PR response. If you need the ID (e.g. to filter or to pass to another write), call into `gitea-labels-milestones` and match by title via `milestone_read method: "list"`. +- **CI status and review/approval state are independent merge gates.** `get_status` only reports CI. Branch-protection rules (required approvals, requested-reviewer coverage, stale-approval handling) are enforced server-side by the merge call itself and will error if unmet — passing CI does not mean the merge will succeed. +- **Reviews move through a state machine, not a single write.** `create` opens a review in `PENDING` state with inline comments attached; `submit` finalizes it with a terminal `state` (`APPROVED`/`REQUEST_CHANGES`/`COMMENT`). A submitted review can be `dismiss`ed afterward, but never deleted — `delete` only removes a review that was never submitted. +- **Merging a PR does not auto-close linked issues.** Unlike GitHub, Gitea has no merge-triggers-close event. It does parse closing keywords (`Fixes #N`, `Closes #N`) in commit messages landing on the default branch, so a non-squash merge that preserves those commit messages may auto-close the issue — but a squash merge rewrites history into one commit, so survival of the keyword depends on the squash commit's message. Always call `issue_read method: "get"` on any referenced issue after merging to check whether it already closed before deciding to close it explicitly. + +## Composing `gitea-labels-milestones` + +Before any `pull_request_write` call that includes a `labels` or `milestone` parameter, resolve names/titles to numeric IDs via `gitea-labels-milestones` — `label_read method: "list_repo_labels"` for label name → ID, `milestone_read method: "list"` for milestone title → ID. Never pass a label name string or milestone title string directly to `pull_request_write`; both parameters take numeric IDs only. This skill does not duplicate that lookup logic — it composes the shared skill. + +## Dispatch + +| Task | Tool | method | +|---|---|---| +| List PRs | `list_pull_requests` | — | +| Get PR details | `pull_request_read` | `"get"` | +| Get PR diff | `pull_request_read` | `"get_diff"` | +| Get PR changed files | `pull_request_read` | `"get_files"` | +| Get PR CI status | `pull_request_read` | `"get_status"` | +| Get PR reviews | `pull_request_read` | `"get_reviews"` | +| Get one review | `pull_request_read` | `"get_review"` | +| Get review inline comments | `pull_request_read` | `"get_review_comments"` | +| Create a PR | `pull_request_write` | `"create"` | +| Update a PR | `pull_request_write` | `"update"` | +| Close a PR | `pull_request_write` | `"close"` | +| Reopen a PR | `pull_request_write` | `"reopen"` | +| Merge a PR | `pull_request_write` | `"merge"` | +| Update branch from base | `pull_request_write` | `"update_branch"` | +| Add reviewers | `pull_request_write` | `"add_reviewers"` | +| Remove reviewers | `pull_request_write` | `"remove_reviewers"` | +| Create a review | `pull_request_review_write` | `"create"` | +| Submit a review | `pull_request_review_write` | `"submit"` | +| Delete a review | `pull_request_review_write` | `"delete"` | +| Dismiss a review | `pull_request_review_write` | `"dismiss"` | + +For full parameter detail on listing/reading/creating/updating/closing PRs, read `references/pull-requests.md`. For review-specific detail (create/submit/delete/dismiss, inline comment shape), read `references/reviews.md`. For the merge workflow specifically (CI gate, merge styles, branch cleanup, post-merge issue check), read `references/merging.md`. diff --git a/plugins/gitea/skills/gitea-prs/references/merging.md b/plugins/gitea/skills/gitea-prs/references/merging.md new file mode 100644 index 0000000..2be8d06 --- /dev/null +++ b/plugins/gitea/skills/gitea-prs/references/merging.md @@ -0,0 +1,41 @@ +--- +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. diff --git a/plugins/gitea/skills/gitea-prs/references/pull-requests.md b/plugins/gitea/skills/gitea-prs/references/pull-requests.md new file mode 100644 index 0000000..93986e6 --- /dev/null +++ b/plugins/gitea/skills/gitea-prs/references/pull-requests.md @@ -0,0 +1,75 @@ +--- +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 (e.g. a prior `type` parameter that no longer exists on `list_issues`, and the `review_scomments` typo covered in `references/reviews.md`). Re-verify via `ToolSearch` before trusting this file if the gitea-mcp version changes. + +## `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"` and `"get_review_comments"`; see `references/reviews.md` +- `binary` (boolean, optional) — include binary diff content for `"get_diff"` +- `page` (number, optional, default 1) +- `per_page` (number, optional, default 30) + +`"get"`, `"get_diff"`, `"get_files"`, and `"get_status"` are covered here. `"get_reviews"`, `"get_review"`, and `"get_review_comments"` are covered in `references/reviews.md`. + +- `"get"` returns the full PR object: state, draft, merged, mergeable flags; `head`/`base` as full objects (`{ref, sha, repo?}`); `milestone` as a bare title string (not `{id, title}`); `review_scomments` (typo, see `references/reviews.md`). +- `"get_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. + +## `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"`) — target branch +- `assignee` (string, optional) — single login +- `assignees` (array of strings, optional) — login names +- `milestone` (number, optional) — milestone ID, never a title; settable on both `"create"` and `"update"` +- `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) +- `reviewers` (array of strings, optional) — login names; settable directly on `"create"`, or use `"add_reviewers"`/`"remove_reviewers"` to adjust reviewers on an already-open PR +- `team_reviewers` (array of strings, optional) — same as `reviewers`: settable on `"create"`, or via `"add_reviewers"`/`"remove_reviewers"` post-creation +- `draft` (boolean, optional, for `"create"`) — prepends `"WIP:"` to the title (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`. + +**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 `title` set to the same text minus the `"WIP:"` prefix; there is no dedicated undraft method. + +**`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. diff --git a/plugins/gitea/skills/gitea-prs/references/reviews.md b/plugins/gitea/skills/gitea-prs/references/reviews.md new file mode 100644 index 0000000..e21d23d --- /dev/null +++ b/plugins/gitea/skills/gitea-prs/references/reviews.md @@ -0,0 +1,43 @@ +--- +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`. + +## 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"` +- `owner` (string, required) +- `repo` (string, required) +- `pull_number` (number, required) +- `review_id` (number, required for every method except `"create"`, which returns the ID to use for the follow-up `submit`/`delete`/`dismiss` call) +- `state` (string, optional) — `"APPROVED"` | `"REQUEST_CHANGES"` | `"COMMENT"` | `"PENDING"` — set on `"create"` (typically `"PENDING"`, or a terminal state to create-and-submit in one call if the server supports it) or `"submit"` (terminal state) +- `body` (string, optional) — overall review comment text +- `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 + +## Reading reviews (`pull_request_read`) + +- `method: "get_reviews"` — array of review summaries: `id`, `state`, `body`, `user` (login), `comments_count`, `submitted_at`, `html_url`, `stale` (bool — the PR was pushed to after this review was submitted, meaning it may be outdated), `official` (bool), `dismissed` (bool). +- `method: "get_review"` (requires `review_id`) — single review detail. +- `method: "get_review_comments"` (requires `review_id`) — array of inline comments: `id`, `body`, `path`, `position`, `old_position`, `diff_hunk`, `user`, `html_url`, `created_at`, `updated_at`. + +**`review_scomments` typo:** the full PR object returned by `pull_request_read method: "get"` includes a field named `review_scomments` (a count), not `review_comments` — a source-level misspelling in gitea-mcp v1.3.0's `slim.go`. Do not write code or instructions that reference `review_comments` on that response; it will always be `undefined`. This is distinct from the `get_review_comments` method above, which is spelled correctly and returns the actual comment objects. + +**Inline-comment field names differ between write and read.** The `comments` array on `pull_request_review_write method: "create"` uses `old_line_num`/`new_line_num`. The `get_review_comments` read response uses different field names for the same concept — `position` (new-side line) and `old_position` (old-side line). Do not assume the same key names apply on both sides of the round trip. diff --git a/plugins/gitea/skills/gitea-prs/references/sources.md b/plugins/gitea/skills/gitea-prs/references/sources.md new file mode 100644 index 0000000..90d86a6 --- /dev/null +++ b/plugins/gitea/skills/gitea-prs/references/sources.md @@ -0,0 +1,33 @@ +# 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. Live tool schemas (`list_pull_requests`, `pull_request_read`, `pull_request_write`, `pull_request_review_write`) were verified directly against the deployed MCP server via `ToolSearch` at authoring time, per this repo's process for resolving schema-vs-docs drift, rather than copied from the derived research doc. +- **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 `review_scomments` typo and the PR-response milestone-as-title-string quirk. +- **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:** SKILL.md, 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` diff --git a/plugins/gitea/skills/gitea-releases/README.md b/plugins/gitea/skills/gitea-releases/README.md new file mode 100644 index 0000000..4a23a7d --- /dev/null +++ b/plugins/gitea/skills/gitea-releases/README.md @@ -0,0 +1,24 @@ +# gitea-releases + +Manage Gitea releases and tags — list, create, and delete releases (with draft/prerelease flags and notes) and their underlying tags. + +## What it does + +This skill handles release and tag operations for a Gitea repository. It creates releases from a tag/target commitish with title, notes, and draft/prerelease flags; lists and paginates releases and tags; retrieves the latest release; and deletes releases and tags as separate, independent destructive operations. It resolves the numeric release id required for deletion instead of assuming a tag name will work. + +## Usage + +``` +/gitea-releases +``` + +Describe your release/tag task: list releases, get the latest release, create a release (with a tag, target, and title), or delete a release or tag. The skill handles resolving the numeric release id where required and keeps release/tag deletion as distinct operations. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents | +| `references/call-signatures.md` | Tool parameters and response shapes derived from gitea-mcp source (see `references/sources.md`); input params for 3 of the 9 tools additionally live-cross-checked | +| `references/conventions.md` | Semver/draft/prerelease practitioner conventions and pagination behavior | +| `references/sources.md` | Research sources backing the call signatures and conventions | diff --git a/plugins/gitea/skills/gitea-releases/SKILL.md b/plugins/gitea/skills/gitea-releases/SKILL.md new file mode 100644 index 0000000..cee7096 --- /dev/null +++ b/plugins/gitea/skills/gitea-releases/SKILL.md @@ -0,0 +1,52 @@ +--- +name: gitea-releases + +description: > + Use when managing Gitea releases and tags for a repository: listing, creating, or deleting + releases (with draft/prerelease flags and release notes), and listing, creating, or deleting the + underlying git tags. Use even if the user doesn't say "release" explicitly — "cut a v1.2.0", + "publish a prerelease", "tag this commit", or "what's the latest release" all apply. Do not use + for git branch or commit history operations (use gitea-branches) or for issue/PR management (use + gitea-issues / gitea-prs). + +metadata: + category: gitea + source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go + - context7-websites-gitea + - context7-gitea-tea-cli +--- + +## Gotchas + +- **`delete_release` takes a numeric `id`, never a tag name.** `delete_tag` is the mirror opposite — it takes the `tag_name` string, never a numeric id. These two tools are asymmetric on purpose; passing a tag name to `delete_release` or a numeric id to `delete_tag` fails. Always resolve the numeric release id via `list_releases` or `get_release` first if you only have a tag name in hand. +- **Deleting a release does not delete its tag.** They are separate destructive operations against separate resources — a release is a wrapper (title, notes, draft/prerelease flags, assets) around a tag, not the tag itself. If the intent is to remove both, call `delete_release` and `delete_tag` separately. +- **`list_releases`/`list_tags` default to `per_page: 20`**, unlike most other gitea-mcp tools which default to 30. There is no auto-pagination in the MCP layer — to get a complete result set, loop `page` upward until a page returns fewer than `per_page` results. +- **`is_draft`/`is_pre_release` are explicit booleans the caller sets on `create_release` — never inferred from `tag_name`.** Note the input param is `is_draft`, which maps to the `draft` field on the *response* object (see Dispatch table below and `references/call-signatures.md`) — `draft` is never a valid input key. Practitioner convention (per the `tea` CLI) uses `-beta`/`-rc` suffixes for prereleases (e.g. `v2.0.0-beta.1`), but Gitea does not enforce or infer this from the tag string. If the user names a tag that looks like a prerelease, set `is_pre_release: true` explicitly rather than assuming the flag is redundant with the name. +- **Tag names are conventionally semver, `v`-prefixed** (`v1.2.0`, `v2.0.0-beta.1`), but this is a practitioner convention, not a Gitea constraint — don't reject or rewrite a caller-supplied tag name that doesn't follow it. + +## Dispatch table + +| 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. See `references/call-signatures.md` for response shapes. + +## Workflow + +- [ ] **Creating a release:** Call `create_release` directly with `tag_name` + `target` + `title` — Gitea is assumed to create the underlying tag automatically if `tag_name` doesn't already exist (this is plausible behavior inferred from the API shape, not directly confirmed in the research docs), so a separate `create_tag` call is only needed when you want to tag a commit without wrapping it in a release yet. Verify the tag exists afterward if this matters to the caller. Set `is_pre_release`/`is_draft` explicitly per the Gotchas above; don't leave them to default inference. +- [ ] **Deleting a release safely:** Resolve the numeric id first — call `list_releases` (paginate if needed, see Gotchas) or `get_release` if the id is already known, find the entry matching the target `tag_name`, then call `delete_release` with that `id`. Never pass `tag_name` to `delete_release`. +- [ ] **Deleting a tag along with its release:** Delete the release first (frees the id lookup), then call `delete_tag` with the `tag_name` separately — confirm both are intended before proceeding, since each is an independent irreversible operation. +- [ ] **Listing completely:** If the caller needs all releases or tags (not just the first page), loop `page: 1, 2, 3...` until a response has fewer than `per_page` entries. + +If exact response field shapes or additional conventions are needed, read `references/call-signatures.md` and `references/conventions.md`. diff --git a/plugins/gitea/skills/gitea-releases/references/call-signatures.md b/plugins/gitea/skills/gitea-releases/references/call-signatures.md new file mode 100644 index 0000000..62667db --- /dev/null +++ b/plugins/gitea/skills/gitea-releases/references/call-signatures.md @@ -0,0 +1,75 @@ +--- +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 3 of the 9 tools here — `create_release`, `delete_tag`, and +`get_latest_release` — were additionally cross-checked live via `ToolSearch` against the deployed +`mcp__gitea__*` tools in session 2026-07-05, and confirmed to match exactly (required/optional +params and names). That check covered only input params for those 3 tools, not response shapes, +and not the other 6 tools — treat the rest of this document as source-derived, not live-verified. + +`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. It is assumed (by analogy with typical "latest release" semantics) that this excludes drafts and prereleases, but that exclusion is not directly confirmed by any of the research docs — verify with `list_releases` if the caller depends on this. + +**`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): +``` +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 + +None of the list tools auto-paginate. To collect a full result set, call with `page: 1`, then +`page: 2`, etc., stopping when a page returns fewer items than `per_page`. `list_releases` and +`list_tags` default `per_page` to 20 — lower than the 30-default used by most other gitea-mcp list +tools, so a caller assuming 30 will under-count pages needed for a fixed total. diff --git a/plugins/gitea/skills/gitea-releases/references/conventions.md b/plugins/gitea/skills/gitea-releases/references/conventions.md new file mode 100644 index 0000000..b8cf75f --- /dev/null +++ b/plugins/gitea/skills/gitea-releases/references/conventions.md @@ -0,0 +1,44 @@ +--- +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 an existing or +newly-created tag. The tag is the git-level object (a name pointing at a commit); the release is a +Gitea-level metadata wrapper around it. This is why `delete_release` and `delete_tag` are separate +calls with separate identifiers (numeric id vs. tag name) — removing the wrapper never implies +removing the underlying pointer, and vice versa. + +## 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. + +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. diff --git a/plugins/gitea/skills/gitea-releases/references/sources.md b/plugins/gitea/skills/gitea-releases/references/sources.md new file mode 100644 index 0000000..2b67a9f --- /dev/null +++ b/plugins/gitea/skills/gitea-releases/references/sources.md @@ -0,0 +1,48 @@ +# 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/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:** +- SKILL.md (Gotchas — semver tag naming) +- references/conventions.md + +**Status:** `extracted` diff --git a/plugins/gitea/skills/gitea-workflow/README.md b/plugins/gitea/skills/gitea-workflow/README.md new file mode 100644 index 0000000..4c81d20 --- /dev/null +++ b/plugins/gitea/skills/gitea-workflow/README.md @@ -0,0 +1,22 @@ +# gitea-workflow + +Human-facing entry point and router for the Gitea integration. + +## What it does + +This skill is the conversational front door to the Gitea suite — it replaces the old flat `/gitea` skill. On its own it never calls a Gitea MCP tool; it composes the six domain skills (`gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, `gitea-releases`). It handles the no-args status check-in (open issues + open PRs), resolves ambiguous issue-or-PR numbers before dispatching (issues and PRs share one number space), and points a user or agent to the right domain skill when it's unclear which one applies. + +## Usage + +``` +/gitea-workflow +``` + +Invoke with no arguments for a status check-in, with a bare number to resolve and show issue or PR detail, or with a general request to be routed to the right domain skill. + +## Files + +| File | Purpose | +|------|---------| +| `SKILL.md` | Skill instructions for agents — Gotchas, status view, ambiguous-number resolution, and the domain-skill index | +| `references/sources.md` | Research sources backing the routing/status guidance | diff --git a/plugins/gitea/skills/gitea-workflow/SKILL.md b/plugins/gitea/skills/gitea-workflow/SKILL.md new file mode 100644 index 0000000..92b7a6e --- /dev/null +++ b/plugins/gitea/skills/gitea-workflow/SKILL.md @@ -0,0 +1,74 @@ +--- +name: gitea-workflow + +description: > + Use when a human wants a general or ambiguous Gitea status check or isn't sure which Gitea + domain skill applies — a no-args check-in ("what's going on in the repo", "any updates?"), + a bare-numbered reference that could be an issue or a PR ("what's the status of #42", "what's + happening with #17"), or a request to discover which Gitea capability handles a task. This is + the human-facing entry point and router for the Gitea integration — it replaces the old flat + `/gitea` invocation (now `/gitea-workflow`) and composes the six domain skills + (`gitea-issues`, `gitea-labels-milestones`, `gitea-prs`, `gitea-branches`, `gitea-files`, + `gitea-releases`) rather than calling any Gitea MCP tool directly. Do not use this skill when + the domain is already known and unambiguous — invoke the matching domain skill directly instead + (e.g. "create an issue" → `gitea-issues`, "merge PR #10" → `gitea-prs`, "cut a release" → + `gitea-releases`). Do not use for local git operations with no Gitea component (use + `git-workflow`). + +compatibility: Requires Gitea MCP server configured with a token; delegates all calls to the six + domain skills, which in turn require write:issue and write:repository scopes at minimum. + +metadata: + category: integration + version: "0.1.0" + source_keys: + - gitea-mcp-repo + - gitea-mcp-slim-go + - context7-websites-gitea +--- + +## Gotchas + +- **This skill never calls a Gitea MCP tool itself.** Every read or write goes through one of the six domain skills. If a request needs a raw `mcp__gitea__*` call that no domain skill exposes, that's a gap in a domain skill, not something to patch here. +- **Issues and PRs share one number space** — a bare number like `#42` could be either. Never guess from context clues alone; resolve it with a real call (see Step 2) before dispatching. +- **A 404 on the resolution call doesn't necessarily mean the number doesn't exist.** Gitea hides permission errors as not-found (documented in `gitea-issues`' Gotchas). If resolution 404s unexpectedly, say so and suggest checking token scope rather than reporting "no such issue or PR." + +## Step 1 — Default status view (no args) + +When invoked with no specific request, give a status check-in: + +1. Invoke `gitea-issues` to list open issues (`state: "open"`). +2. Invoke `gitea-prs` to list open PRs (`state: "open"`). +3. Run both in parallel — they're independent reads. +4. Report as two sections, "Open Issues" and "Open Pull Requests", each as a compact list (number, title). This preserves the original flat `/gitea` skill's default behavior. + +## Step 2 — Resolve an ambiguous number + +When the user references a bare number without saying "issue" or "PR" (e.g. "what's going on with #42"): + +1. Invoke `gitea-issues` to run `issue_read method: "get"` on that number. +2. Check the response's `is_pull` field: + - `true` → it's a PR. Invoke `gitea-prs` for full PR detail (status, diff, reviews as appropriate to the request) and present that instead. + - `false` or absent → it's an issue. Present the issue detail already retrieved. +3. If the resolution call 404s, don't conclude the number doesn't exist — report the 404 and suggest verifying token scope (`write:issue`) per `gitea-issues`' Gotchas, since permission errors are hidden as not-found in Gitea. + +Never dispatch to `gitea-issues` or `gitea-prs` based on guessing from phrasing alone ("that sounds like a bug" is not evidence) — always resolve first. + +## Step 3 — Route explicit but domain-unclear requests + +For requests that name a capability but not obviously which skill owns it, use this index: + +| Skill | Covers | +|---|---| +| `gitea-issues` | List/read/create/update issues, comments, search across issues and PRs. Composes `gitea-labels-milestones` for label/milestone resolution. | +| `gitea-labels-milestones` | Label and milestone CRUD, label inference from conversation context, resolving names/titles to the numeric IDs writes require. Cross-cutting — used by both `gitea-issues` and `gitea-prs`. | +| `gitea-prs` | List/read/create/update/merge PRs, code reviews. Composes `gitea-labels-milestones` the same way `gitea-issues` does. | +| `gitea-branches` | Branch list/create/delete, plus commit history (list commits, get a single commit by SHA). | +| `gitea-files` | Read/write/delete individual files, list a directory, walk the full repo tree. | +| `gitea-releases` | Release and tag CRUD — draft/prerelease flags, release notes, semver tags. | + +If a request clearly names one of these (e.g. "create a milestone" → `gitea-labels-milestones`, "read this file from the repo" → `gitea-files`), invoke that skill directly rather than routing through here. Use this table only when the user or an upstream agent is unsure which skill applies. + +## Step 4 — Report + +Present results in plain language. For the status view, two labeled sections. For a resolved ambiguous number, say which domain it turned out to be before showing detail ("That's a pull request:" / "That's an issue:"). For routing, name the skill and hand off — don't duplicate its output format, let it report. diff --git a/plugins/gitea/skills/gitea-workflow/references/sources.md b/plugins/gitea/skills/gitea-workflow/references/sources.md new file mode 100644 index 0000000..852e7af --- /dev/null +++ b/plugins/gitea/skills/gitea-workflow/references/sources.md @@ -0,0 +1,33 @@ +# 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`), and its ambiguous-number resolution relies on `issue_read`'s `is_pull` field, both verified against this source at authoring time. +- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md +- **Contributing files:** SKILL.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:** SKILL.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:** 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. 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` diff --git a/plugins/gitea/sources.md b/plugins/gitea/sources.md new file mode 100644 index 0000000..03f1b1b --- /dev/null +++ b/plugins/gitea/sources.md @@ -0,0 +1,33 @@ +# Sources + +## gitea-mcp-repo + +- **URL:** https://gitea.com/gitea/gitea-mcp +- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md +- **Description:** Official gitea-mcp repository (v1.3.0); operation/*.go source files documenting all 55 MCP tools, their parameters, and CLI flags — informs the orchestrator's operation index and its `delete_release`/`delete_tag` id-vs-name and pagination-default hard rules. +- **Contributing files:** agents/gitea-orchestrate.md, agents/gitea-orchestrate.agent.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 +- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md +- **Description:** Slim response shape structs from gitea-mcp source; defines the `is_pull` field the orchestrator's Number resolution routine checks to disambiguate issue vs. PR numbers. +- **Contributing files:** agents/gitea-orchestrate.md, agents/gitea-orchestrate.agent.md +- **Status:** `extracted` + +## context7-websites-gitea + +- **URL:** context7:/websites/gitea +- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md +- **Description:** Official Gitea docs mirror on Context7 — informs the default-branch protection hard rule and permission-errors-as-404 behavior the orchestrator surfaces via `not_found_or_forbidden`. +- **Contributing files:** agents/gitea-orchestrate.md, agents/gitea-orchestrate.agent.md +- **Status:** `extracted` + +## context7-gitea-tea-cli + +- **URL:** context7:/git_gitea_com/gitea_tea +- **Research doc:** plugins/gitea/docs/research/docs/gitea/sources.md +- **Description:** Official `tea` CLI docs on Context7 — practitioner conventions for release/tag semver naming and merge strategy choices that inform the orchestrator's destructive-operation gating around `merge-pr` and `delete-release`/`delete-tag`. +- **Contributing files:** agents/gitea-orchestrate.md, agents/gitea-orchestrate.agent.md +- **Status:** `extracted` diff --git a/tests/run-bats.sh b/tests/run-bats.sh index 655baca..5aa0221 100755 --- a/tests/run-bats.sh +++ b/tests/run-bats.sh @@ -20,6 +20,7 @@ mapfile -t TEST_FILES < <( find "$REPO_ROOT" -name "*.bats" \ -not -path "*/tests/bats/*" \ -not -path "*/test_helper/*" \ + -not -path "*/.claude/worktrees/*" \ | sort ) diff --git a/tests/run-tests.sh b/tests/run-tests.sh index dcfea34..51464ab 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -33,6 +33,7 @@ run_bats mapfile -t SCRIPTS < <( find "$SEARCH_ROOT" -name "test-*.sh" \ -not -path "*/.git/*" \ + -not -path "*/.claude/worktrees/*" \ | sort )