refactor(gitea-files): retrofit to the ADR-0020 context contract
Description 787 -> 263 chars, body 922 -> 302 words, Gotchas 9 entries/69% of body -> 3/24.8%. Clears both size FAILs and both Gotchas suggestions. Deleted the second trigger register outright -- ~300 chars re-quoting the same six verbs as user phrasings, which ADR-0020 names this skill for specifically. Split the body on the read/write boundary: one invocation cannot both read and write, so a dispatch table is mandatory. Six operations collapse into two flow files rather than six -- create/update/delete share one tool pair and one SHA-first lifecycle whose preamble would otherwise be triplicated, and the three read tools share ref selection plus a 'neither listing is a SHA source' comparison that only exists between them. references/examples.md removed; all eight of its content blocks and all nine named parameters carry into references/writing.md, verified against git HEAD. Two deltas are corrections: the repo tree is now ruled out as a SHA source, and reusing a SHA captured earlier in the conversation is now forbidden. Four Gotchas relocated to the flow file that needs them; two promoted to gates (SHA-as-concurrency-token opens writing.md; owner/repo became ## Inputs). Refs #99
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
---
|
||||
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: "<base64-encoded content>"
|
||||
message: "docs: add example"
|
||||
branch_name: "main"
|
||||
new_branch_name: "feat/add-example" ← branches off before the commit lands
|
||||
(sha omitted — this is a new file)
|
||||
|
||||
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: "<new base64-encoded content>"
|
||||
message: "docs: update example"
|
||||
branch_name: "main"
|
||||
sha: "<sha from step 1>"
|
||||
```
|
||||
|
||||
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: "<sha from step 1>"
|
||||
```
|
||||
46
plugins/gitea/.apm/skills/gitea-files/references/reading.md
Normal file
46
plugins/gitea/.apm/skills/gitea-files/references/reading.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
source_keys:
|
||||
- gitea-mcp-repo
|
||||
- gitea-mcp-slim-go
|
||||
---
|
||||
|
||||
# Reading files, directories and trees
|
||||
|
||||
All three read calls select what to read with `ref` — a branch name, tag, or commit SHA. On
|
||||
`get_repository_tree` the same value goes in `tree_sha` despite the name.
|
||||
|
||||
## Single file
|
||||
|
||||
`get_file_contents(owner, repo, ref, path)`.
|
||||
|
||||
The response carries the file's `sha` at the **top level**, not nested under `content`. That field
|
||||
is the write-ready SHA, so capture it whenever a write may follow. Content comes back
|
||||
base64-encoded — decode it. Pass `withLines: true` only when you need numbered lines to quote
|
||||
specific lines back to the user; omit it for a normal content fetch.
|
||||
|
||||
## One directory level
|
||||
|
||||
`get_dir_contents(owner, repo, ref, path)` returns the immediate entries only — name, path, type,
|
||||
size. No recursion, no content, no `sha`.
|
||||
|
||||
## Whole repository tree
|
||||
|
||||
`get_repository_tree(owner, repo, tree_sha, recursive)`. Set `recursive: true` to walk
|
||||
subdirectories in one call.
|
||||
|
||||
The response sets `truncated: true` when one page does not hold every entry. Page through with
|
||||
`page`/`per_page` (defaults `1` and `30`) until a page returns fewer entries than `per_page`.
|
||||
|
||||
## Neither listing is a SHA source for a write
|
||||
|
||||
`get_dir_contents` entries carry no `sha` at all. `get_repository_tree` entries do carry a blob or
|
||||
tree hash, but reaching it costs an extra round trip and returns no content. `get_file_contents` is
|
||||
the canonical path for a write's SHA: one call returns the decoded content and the write-ready
|
||||
`sha` together.
|
||||
|
||||
## A 404 that is really a 403
|
||||
|
||||
These reads gate on `write:repository`, not on read access alone, and some Gitea endpoints answer
|
||||
an under-scoped token with 404 instead of 403 so they do not leak whether the resource exists. A
|
||||
404 on a path you are confident about is a scope problem until proven otherwise — check the token's
|
||||
configured scopes before concluding the file or directory does not exist.
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
- **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)
|
||||
**Contributing files:** (tool parameters and SHA/concurrency behavior cross-checked live against the deployed MCP tool schemas via ToolSearch)
|
||||
- SKILL.md (Gotchas — cross-flow parameter and encoding traps; Dispatch)
|
||||
- references/reading.md (read-tool parameters, `ref`/`tree_sha` selection, tree pagination)
|
||||
- references/writing.md (write-tool parameters, SHA/concurrency behavior, canonical call sequences, failed-write triage)
|
||||
|
||||
## gitea-mcp-slim-go
|
||||
|
||||
@@ -21,8 +22,9 @@
|
||||
- **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)
|
||||
- SKILL.md (Gotchas — base64 response encoding)
|
||||
- references/reading.md (top-level `sha` field location, `get_dir_contents`/`get_repository_tree` not being usable SHA sources for a file write)
|
||||
- references/writing.md (SHA-first update/delete sequences)
|
||||
|
||||
## context7-websites-gitea
|
||||
|
||||
@@ -33,7 +35,7 @@
|
||||
- **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")
|
||||
- SKILL.md (Handoff — committing straight to a branch is the normal path, not an escape hatch)
|
||||
|
||||
## context7-gitea-tea-cli
|
||||
|
||||
|
||||
76
plugins/gitea/.apm/skills/gitea-files/references/writing.md
Normal file
76
plugins/gitea/.apm/skills/gitea-files/references/writing.md
Normal file
@@ -0,0 +1,76 @@
|
||||
---
|
||||
source_keys:
|
||||
- gitea-mcp-repo
|
||||
- gitea-mcp-slim-go
|
||||
---
|
||||
|
||||
# Creating, updating and deleting files
|
||||
|
||||
`sha` is the optimistic-concurrency token for every write, and it comes from
|
||||
`get_file_contents`'s **top-level** `sha` field — never from `content.sha`, a directory listing, or
|
||||
a tree entry. Do not guess or reuse a stale value: a mismatched SHA is rejected exactly like a
|
||||
missing one.
|
||||
|
||||
`content` is base64-encoded, and the branch the commit lands on is `branch_name`, not `ref`.
|
||||
|
||||
## Create a new file
|
||||
|
||||
Call `create_or_update_file(owner, repo, path, content, message, branch_name)` with `sha` omitted
|
||||
entirely. An omitted `sha` always means *create*, so the call returns HTTP 409 if the path already
|
||||
exists.
|
||||
|
||||
To branch off as part of the same write, pass `new_branch_name`: the branch is created and the
|
||||
commit lands on it in one call, replacing a separate branch-creation step.
|
||||
|
||||
## Update an existing file
|
||||
|
||||
1. `get_file_contents(owner, repo, ref: <branch>, path)` → read the top-level `sha`.
|
||||
2. `create_or_update_file(owner, repo, path, content, message, branch_name, sha: <that value>)`.
|
||||
|
||||
## Delete a file
|
||||
|
||||
Same SHA-first pattern, with no create-style fallback — `delete_file` without `sha` returns
|
||||
HTTP 422.
|
||||
|
||||
1. `get_file_contents(owner, repo, ref: <branch>, path)` → read the top-level `sha`.
|
||||
2. `delete_file(owner, repo, path, message, branch_name, sha: <that value>)`.
|
||||
|
||||
## Worked sequence — new file on a new branch, then a PR
|
||||
|
||||
```
|
||||
1. create_or_update_file
|
||||
owner, repo
|
||||
path: "docs/example.md"
|
||||
content: "<base64-encoded content>"
|
||||
message: "docs: add example"
|
||||
branch_name: "main"
|
||||
new_branch_name: "feat/add-example" ← branches off before the commit lands
|
||||
(sha omitted — this is a new file)
|
||||
|
||||
2. Hand off to gitea-prs to open a PR from "feat/add-example" into "main".
|
||||
```
|
||||
|
||||
Step 1 needs no preceding read: a brand-new path has no SHA. Fetch the current file first only
|
||||
when the write replaces an existing one.
|
||||
|
||||
## Triaging a failed write
|
||||
|
||||
| Symptom | Cause | Action |
|
||||
|---|---|---|
|
||||
| HTTP 409 | `sha` omitted on a path that already exists | Fetch the current SHA, retry as an update |
|
||||
| HTTP 422 | `sha` missing or stale | Re-fetch the SHA immediately before the write |
|
||||
| 403 or 422 with no SHA explanation | Branch protection requires signed commits | Stop and report |
|
||||
| HTTP 413 | Reverse-proxy body limit in front of Gitea | Report; retrying cannot fix it |
|
||||
| HTTP 404 | Wrong path, or a token without `write:repository` | Verify the path, then the token's scopes |
|
||||
|
||||
**Signed commits.** These writes create commits server-side from a bare API token with no 2FA or
|
||||
PGP context. If the target branch's protection rule requires signed commits, Gitea rejects the
|
||||
write as a generic 403 or 422 that never names signing, and reads against that same branch keep
|
||||
succeeding right up until the write. When a write fails without a clean 409 or 404 explanation,
|
||||
check the branch's protection rule before assuming the SHA is wrong and retrying.
|
||||
|
||||
**Payload size.** base64 inflates `content` roughly 33% over the raw file size, and a 413 is
|
||||
usually a reverse-proxy body-size limit in front of the Gitea instance rather than a Gitea-side
|
||||
rejection. No amount of retrying, or changing the SHA, path, or branch, will fix it — the proxy's
|
||||
config has to be raised, which is outside this skill's control. Surface that distinction instead
|
||||
of retrying the same call.
|
||||
Reference in New Issue
Block a user