refactor(skills): retrofit the corpus to the ADR-0020 context contract (#129)
Retrofits all 39 skills to ADR-0020's description/body context contract, then fixes what six rounds of independent review found in that retrofit — including four ways the hot gate itself failed open. Closes #99, #107, #108, #110, #111, #114, #115, #120. ## The retrofit (waves 1-5) | | Start | Now | |---|---|---| | Description FAILs (>400 chars) | 26 | **0** | | Body FAILs (>900 words, body-only) | 9 | **0** | | Dangling routing targets | 2 | **0** | | `Kyberforge.CompositionNote` | 10 | **0** | | Preload tax | 21,005 chars | **~10,500** | Under the 12,000-char success criterion. Per-wave detail is on #99. ## The review fixes **The gate failed open four ways, three of them found after the retrofit shipped.** An unrecognised follower token made a dangling target vanish. A skill directory with no `SKILL.md` resolved as a valid target, so a commit could be green locally and red in a fresh clone — three existing fixtures were relying on that, one of which made the install-leak A/B pass vacuously. Then the free-standing `/name` sweep turned out to be gated on the sentence carrying a boundary marker, so route notation in any other sentence was invisible — not an ERROR, not a SUGGESTION, not an INFO — which left the documented "`/name` always blocks" promise false from a second direction. All four fixed and pinned. **Two checks were silently not running.** `validate-provenance.sh` checks 7-8 were dead across nine skills. Waking them exposed a deeper problem: they assume `Research doc:` names a source index, but 30 of 121 entries point at topic content documents, so every new check-7 INFO was a false positive and check 8 was saved from a false-FAIL flood only by an *unannounced* skip. Checks 7/8 are now scoped to source indexes and every skip announces itself (#121). **The retrofit's own anti-goal, four times.** ADR-0020 warns that a blunt gate gets satisfied by deleting content rather than relocating it. `diagnose` and `skill-audit` relocated prose and then read it unconditionally; `prototype` and `vale-config` deleted rules outright that survived nowhere. All four addressed. ## Verification - `bash tests/run-tests.sh --strict` — 24 suites, 0 skipped, 0 failed - `bash tests/run-bats.sh` — 325 tests, 0 failures - `pre-commit run --all-files` — 17/17 - `pre-commit run --hook-stage pre-push --all-files` — 16/16, with `apm marketplace check` and `apm pack --check-clean` run against the remote, not skipped - `scripts/skill-size-check.sh` over all 39 skills — rc 0, 0 ERROR/FAIL, SUGGESTION-only - Preload tax measured at **10,498 chars**, max description 390 — both inside budget - Every new test proven non-vacuous by a deliberate mutation of the behaviour it covers **Per-commit sync, stated accurately:** the ten commits from the latest review round each pass `check-plugin-content-sync` in isolation, verified by checking each out in a detached worktree with a clean between. The earlier gitea window (`dfacf05..bedbd1d`, nine commits) does **not** — its mirror was regenerated in one batch at `bbc7300`. An earlier revision of this description claimed the property held for every commit; it does not, and a bisect through that window lands on a red commit. **Squash-merge** to collapse it, or accept that this range is not bisectable. ## Version bump Six plugins and the catalog take a **patch**, not a minor. The branch is **89 commits — 40 `fix` / 30 `refactor` / 12 `docs` / 5 `chore` / 2 `test` — zero `feat`, zero `!`, zero `BREAKING CHANGE`** — and adds no skill, agent, command or hook. (Two earlier revisions of this section cited a stale histogram, most recently 78 commits; the figures above are measured at HEAD.) Both rules this repo ships (`forge/references/version-bump.md`, landing in this PR, and `git-commits/references/conventional-commits-spec.md`) make that a patch, and the catalog set is unchanged at 7 entries. Not settled by that: four published files were removed from the installed tree, three moved, and `caveman` gained `disable-model-invocation`, retiring its old triggers. Under a strict reading those are major-class and currently ship under `refactor:` with no marker. Whether the deployed skill surface is a public contract is written down nowhere — worth deciding, but it outlives this PR. ## Deliberately not in scope #112 (cherry-pick ownership, now resolved in favour of `git-commits`), #113 (`rtk git` normalisation), #116 (research fan-out), #101 (audit-skill merge), #122 (non-spec skill-root files), #123 (no PRD producer) stay open. #117 is the one worth reading: the contract's remedy is to move prose into `references/`, which is exactly where neither the size gate nor Vale looks — and the blind spot is wider than #117 currently records, since there is no root `.vale.ini` at all, so every ADR, `CONTEXT.md` and `README.md` is unlinted too. That blind spot let this branch carry two `level: error` `Kyberforge.SentenceOpenerThereIs` violations into `references/` files it created — `provider-adapter-author/references/provider-matrix.md:31` and `agent-audit/references/finding-criteria.md:95`. Both are reworded in `afadaae`, confirmed by routing each file through the audit's own `vale-wrap.sh` (1 error each before, 0 after). Five further occurrences sit in `references/` files already on `main`; those are the pre-existing corpus and stay with #117, which is the real fix. Also unfixed and not this PR's: `apm install` appends a duplicate `SessionStart` entry to `.claude/settings.json`, so a fresh clone cannot get pre-push green without an edit AGENTS.md warns against. Reproduces identically on `main`. Co-authored-by: Defame1297 <gitea@rkdr.net> Reviewed-on: https://git.dev.rkdr.net/Defame1297/holocron/pulls/129 Co-authored-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net> Co-committed-by: Claude Code AI - Gitea MCP <claude@noreply.git.dev.rkdr.net>
This commit was merged in pull request #129.
This commit is contained in:
@@ -6,9 +6,15 @@ Manage Gitea releases and tags — list, create, and delete releases (with draft
|
||||
|
||||
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.
|
||||
|
||||
## Before you start
|
||||
|
||||
Requires a Gitea MCP server configured with a token holding `write:repository`. Requires a git remote
|
||||
named `origin` pointing at the Gitea instance, unless an orchestrating caller already resolved
|
||||
`owner`/`repo` for you.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
```text
|
||||
/gitea-releases
|
||||
```
|
||||
|
||||
@@ -19,6 +25,6 @@ Describe your release/tag task: list releases, get the latest release, create a
|
||||
| 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/call-signatures.md` | Tool parameters and response shapes derived from gitea-mcp source (see `references/sources.md`); input params for all nine tools additionally cross-checked live against gitea-mcp v1.7.0 |
|
||||
| `references/conventions.md` | Semver/draft/prerelease practitioner conventions and pagination behavior |
|
||||
| `references/sources.md` | Research sources backing the call signatures and conventions |
|
||||
|
||||
@@ -2,31 +2,46 @@
|
||||
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).
|
||||
Use when managing Gitea releases or the git tags underneath them — list, get, create, or
|
||||
delete either — even when the user does not say "release" or "Gitea": "cut a v1.2.0",
|
||||
"publish a prerelease", "tag this commit", "what's the latest release". Not branches or
|
||||
commit history -> `gitea-branches`. Not issues -> `gitea-issues`. Not pull requests ->
|
||||
`gitea-prs`.
|
||||
|
||||
compatibility: Requires Gitea MCP server configured with a token with write:repository scope, which
|
||||
gates every release and tag tool here. Requires git remote "origin" pointing to the Gitea instance
|
||||
for owner/repo resolution, unless an orchestrating caller passes them already resolved.
|
||||
|
||||
metadata:
|
||||
category: gitea
|
||||
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_releases mcp__gitea__get_release mcp__gitea__get_latest_release mcp__gitea__create_release mcp__gitea__delete_release mcp__gitea__list_tags mcp__gitea__get_tag mcp__gitea__create_tag mcp__gitea__delete_tag
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
|
||||
- **`delete_release` takes 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. The MCP layer does no auto-pagination — 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.
|
||||
- **`delete_release` takes the numeric `id`, never a `tag_name`; `delete_tag` takes the tag name, never an id.** Holding only a tag name, resolve the release id through `list_releases` or `get_release` first — a tag name passed to `delete_release` fails, and that failure is not evidence the release is already gone.
|
||||
- **Deleting a release never deletes its tag**, and the reverse direction is *unconfirmed* — verify with `list_releases`/`get_release` after `delete_tag`. Removing both takes two independent destructive calls.
|
||||
- **Set `is_draft`/`is_pre_release` explicitly on every `create_release`** — Gitea infers neither from a `-beta`/`-rc` tag name, so `v2.0.0-beta.1` publishes as a full release and becomes the repo's latest. `draft`/`prerelease` are output field names only; passing `draft` as an input key is silently ignored.
|
||||
- **`list_releases`/`list_tags` default `per_page` to 20**, where most other gitea-mcp list tools default to 30 — a caller assuming 30 under-counts pages.
|
||||
|
||||
## Dispatch table
|
||||
## Step 1 — Resolve owner and repo
|
||||
|
||||
`owner` and `repo` are required on every tool below. Extract them from the git remote, unless an orchestrating caller passed them in already:
|
||||
|
||||
```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
|
||||
|
||||
| Action | Tool | Required params | Optional params |
|
||||
|---|---|---|---|
|
||||
@@ -40,13 +55,19 @@ metadata:
|
||||
| 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.
|
||||
`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.
|
||||
|
||||
## Workflow
|
||||
Pass a caller-supplied `tag_name` through verbatim — the API accepts any string; semver is convention, not constraint.
|
||||
|
||||
- [ ] **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 every page:** 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.
|
||||
## Step 3 — Procedure for the scenario in hand
|
||||
|
||||
If exact response field shapes or additional conventions are needed, read `references/call-signatures.md` and `references/conventions.md`.
|
||||
These four are mutually exclusive — pick the one row the request lands on.
|
||||
|
||||
| Scenario | Procedure |
|
||||
|---|---|
|
||||
| Create a release | Call `create_release` with `tag_name`, `target`, `title`, and `is_draft`/`is_pre_release` set explicitly — never left to default. This surface carries no update or edit tool, so a wrong flag is repairable only by delete-and-recreate (`references/conventions.md`). A separate `create_tag` is only needed to tag a commit without wrapping it in a release — whether `create_release` creates a missing tag is unconfirmed, so verify with `get_tag`. |
|
||||
| Delete a release | Resolve the numeric `id` per the first Gotcha, confirm intent, then call `delete_release`. The tag survives. |
|
||||
| Delete a tag along with its release | Delete the release first, then call `delete_tag` — confirm both are intended before proceeding, since each is irreversible on its own. |
|
||||
| List every page | Loop `page: 1, 2, 3...` until a response returns fewer than `per_page` entries. Nothing here auto-paginates. |
|
||||
|
||||
If exact input params or response field shapes are needed, read `references/call-signatures.md`. If the caller raises semver tag naming, draft/prerelease semantics, release-notes sourcing, or how a release relates to its tag, read `references/conventions.md`.
|
||||
|
||||
@@ -11,11 +11,12 @@ Signatures and response shapes are derived from gitea-mcp source (`operation/*.g
|
||||
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.
|
||||
Input parameter schemas for all 9 tools here were additionally cross-checked live via `ToolSearch`
|
||||
against the deployed `mcp__gitea__*` tools and confirmed to match exactly — required and optional
|
||||
params, names, and defaults. Last verified against gitea-mcp **v1.7.0**, as reported by
|
||||
`get_gitea_mcp_server_version`. That check covers input params only: the response shapes below
|
||||
remain source-derived, not live-verified, so re-verify them if a response reads differently than
|
||||
documented here.
|
||||
|
||||
`owner` and `repo` are required strings on every tool below and are omitted from the per-tool lists
|
||||
for brevity.
|
||||
@@ -32,7 +33,7 @@ for brevity.
|
||||
|
||||
**`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.
|
||||
- Returns a single release object for the most recently published release. The deployed tool's own description reads "the most recent published (non-draft) release" — it names drafts as excluded and is silent on prereleases, so whether prereleases are also excluded is *unconfirmed*; verify with `list_releases` if the caller depends on it. Either way a release created without `is_pre_release: true` is eligible, which is why that flag has to be set in the create call (see `conventions.md`).
|
||||
|
||||
**`create_release`**
|
||||
- Required: `tag_name` (string), `target` (string — branch, tag, or commit SHA to cut the tag from), `title` (string)
|
||||
@@ -44,7 +45,7 @@ for brevity.
|
||||
- Does not delete the underlying tag.
|
||||
|
||||
**Release object shape** (returned by list/get/create/latest):
|
||||
```
|
||||
```text
|
||||
id, tag_name, target, title, body, draft, prerelease, html_url, author, created_at, published_at
|
||||
```
|
||||
`author` is the creator's login. `body` holds the release notes.
|
||||
|
||||
@@ -12,11 +12,20 @@ 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.
|
||||
A release is a title, body (notes), and draft/prerelease flags layered on top of a tag. The tag is
|
||||
the git-level object (a name pointing at a commit); the release is a Gitea-level metadata wrapper
|
||||
around it. This is why `delete_release` and `delete_tag` are separate calls with separate
|
||||
identifiers (numeric id vs. tag name) — removing the wrapper never implies removing the underlying
|
||||
pointer, and vice versa.
|
||||
|
||||
### Whether `create_release` creates a missing tag is unconfirmed
|
||||
|
||||
`create_release` takes both `tag_name` and `target` (a commitish), and that shape *suggests* Gitea
|
||||
creates the tag at `target` when `tag_name` does not already exist. That is inferred from the API
|
||||
shape, not confirmed by any source this skill was built from (`references/sources.md`) — so treat it
|
||||
as an assumption, not behaviour. When the caller depends on the tag existing, verify it afterward
|
||||
with `get_tag` (or `list_tags`) rather than reporting it as created. `create_tag` is the only call
|
||||
confirmed to create one.
|
||||
|
||||
## Semver tag naming
|
||||
|
||||
@@ -33,6 +42,13 @@ commonly used to signal a prerelease to humans. When a user asks to "cut a beta"
|
||||
release candidate," set `is_pre_release: true` explicitly in the same call rather than relying on
|
||||
the tag string to carry that meaning.
|
||||
|
||||
Getting this wrong is not cheaply repairable. The gitea-mcp release surface is `create_release`,
|
||||
`get_release`, `get_latest_release`, `list_releases` and `delete_release` — there is **no update or
|
||||
edit tool**, so a published release's flags cannot be corrected in place. The only remedy is
|
||||
`delete_release` plus a fresh `create_release`, a destructive round trip; meanwhile a beta published
|
||||
without `is_pre_release` is the release `get_latest_release` returns. Set the flags in the create
|
||||
call.
|
||||
|
||||
Note the input/output naming mismatch: the input param is `is_draft`, but the release object
|
||||
returned by the API uses `draft` (and `prerelease`) as the field names. `draft` is never a valid
|
||||
input key — passing `draft: true` to `create_release` is silently ignored rather than erroring.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## 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.
|
||||
- **Description:** Official gitea-mcp repository; operation/*.go source files documenting the MCP tools, their parameters, and CLI flags. Originally extracted at v1.3.0; the input parameter schemas in `references/call-signatures.md` were re-verified live via `ToolSearch` against the deployed server, **last verified at v1.7.0** as reported by `get_gitea_mcp_server_version`.
|
||||
- **Research doc:** plugins/gitea/docs/research/docs/gitea/api-reference.md (Releases and Tags section); plugins/gitea/docs/research/docs/gitea/troubleshooting.md (`delete_release` numeric-id gotcha, `per_page` defaults)
|
||||
|
||||
**Contributing files:**
|
||||
@@ -42,7 +42,6 @@
|
||||
- **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
|
||||
- references/conventions.md (semver tag naming, release-notes sourcing)
|
||||
|
||||
**Status:** `extracted`
|
||||
|
||||
Reference in New Issue
Block a user