fix(kyberforge): bridge apm content to Claude Code's flat plugin discovery

Claude Code's (and Copilot's) native plugin installer has zero awareness of
.apm/ nesting -- it convention-scans only flat skills/, agents/, commands/,
hooks.json at each plugin's root. Confirmed via strings on the installed
claude binary and live installs of git@holocron/gitea@holocron/kyberforge@
holocron, all reporting Skills(0) Agents(0) Hooks(0) post ADR-0015's apm
conversion. Root cause (apm_cli/core/plugin_manifest.py): apm's plugin.json
compiler deliberately strips skills/agents/commands keys, assuming the host
already auto-discovers those convention directories -- it has no model of
.apm/ being host-visible at all. Separately, apm's own bundle exporter
(apm_cli/bundle/plugin_exporter.py, behind `apm pack --format plugin`)
implements the correct .apm/ -> flat mapping, but only ever targeted
build/<name>-<version>/, a path nothing in marketplace.json's source: points
at.

scripts/sync-plugin-content.sh wraps that bundle exporter and copies its
agents/, skills/, commands/, instructions/, extensions/, and merged
hooks.json back into each plugin's own root as a second tracked
compiled-output category -- same governance status as
.claude-plugin/plugin.json: generated from .apm/, never hand-edited. tests/
subdirectories are excluded from the mirror (dev fixtures, not host-visible
runtime content; several hardcode a relative repo-root walk-up sized for the
.apm/-nested depth, which breaks when duplicated one level shallower).
Applied for real across all 6 plugins and verified two ways: `claude plugin
validate --strict` passes on every real plugin directory, and a live
`claude --plugin-dir <path> -p "list skills/agents"` behavioral test
confirms content is now actually discovered.

Also, from the same issue #90 review round:
- scripts/check-manifests.sh pointed at each plugin's root-level plugin.json
  (checking skills/hooks/mcpServers/agents pointer fields) -- that file was a
  stale near-duplicate of .claude-plugin/plugin.json nothing else read or
  wrote, now deleted across all 6 plugins. check-manifests.sh is rewritten to
  validate .claude-plugin/plugin.json instead, and drops the pointer-field
  checks entirely (nothing to check -- those fields are correctly absent by
  design). Content-presence drift is now check-plugin-content-sync's job, a
  new pre-push hook wired in .pre-commit-config.yaml.

docs/adr/0017 records the root cause and decision in full, including two
rejected alternatives (patching plugin.json's path fields directly -- apm's
compiler strips them on every run; pointing marketplace.json at apm pack's
build/ output -- a version-suffixed non-source directory nothing can install
from without an extra build step). ADR-0015 and CONTEXT.md are updated to
point at it.

Refs: #90
This commit is contained in:
2026-08-13 16:59:03 +00:00
parent 7910b8b12c
commit 38f1ba4e03
217 changed files with 14455 additions and 175 deletions

View File

@@ -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 |

View File

@@ -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 <sha>",
"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 <name> [from <base>]` | Create branch |
| `/gitea-branches delete <name>` | Delete branch |
| `/gitea-branches commits [on <branch>] [touching <path>]` | List commit history |
| `/gitea-branches commit <sha>` | Get full detail for one commit |
For branch operations (list/create/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.

View File

@@ -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: <owner> repo: <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: <owner> repo: <repo> branch: <new-name> old_branch: <source-branch>
```
Default dispatch: if the user gives a base ("branch off of X", "from X"), pass it as `old_branch`.
If they don't specify a base and you're mid-task on a local branch, pass your current branch
(`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: <owner> repo: <repo> branch: <name>
```
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.

View File

@@ -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: <owner> repo: <repo> sha: <branch-or-sha> path: <optional-path>
```
Dispatch defaults:
- "commits on `<branch>`" → pass `<branch>` as `sha`.
- "commits touching `<path>`" (no branch mentioned) → pass `path` alone, `sha` omitted (defaults to
the repo's default branch).
- Both given → pass both; the result is history for that path, walked from that branch/SHA.
- Neither given → omit both; this returns default-branch history, which is a reasonable default for
an open-ended "what's the recent history here" question.
**Response:** one object per commit: `sha`, `html_url`, `created`, `message` (when available),
`author` (`{name, email, date}`, when available).
Paginate per the 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: <owner> repo: <repo> sha: <commit-sha>
```
**Response:** same shape as a `list_commits` entry, but always fully populated (`message` and
`author` are guaranteed present, not conditional). Use this when the user asks about one specific
commit by SHA rather than browsing history — `list_commits` entries may omit `message`/`author` in
edge cases, `get_commit` will not.
## Token scope
Both tools are believed to require `write:repository`, even though they're read-only — inferred by
analogy with the scope-gating principle 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`).

View File

@@ -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`