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..228d33d --- /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.) +- **A closing keyword in a commit message can auto-close an issue without any `issue_write` call.** Gitea has no GitHub-style "merge closes issue" event, but it does parse `Fixes #N`/`Closes #N` in commit messages landing on the default branch. After a PR merges (a `gitea-prs` operation), re-check the issue's state here via `issue_read method: "get"` before deciding whether to close it manually — closing an already-closed issue is a harmless no-op, but don't assume a manual close is always needed. +- **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`