parseArgs now refuses, before any request: two keys equal under
strings.EqualFold in any object (exact duplicates included), and a
top-level key that differs from a schema field only in case. Without
this, encoding/json's case-insensitive last-wins match let
{"branch":"feat/x","Branch":"main"} write to main while a client-side
hook read feat/x.
file_write_branch (branch) and tbd_ship (branch and base) validate the
branch name: ^[A-Za-z0-9][A-Za-z0-9._/-]*$ plus no refs/ prefix, HEAD,
"..", "//", "/.", trailing "/" or ".", or ".lock". refs/heads/main
otherwise created refs/heads/refs/heads/main and wedged API writes to
main. tbd_ship's base is the protection-probe key, so a refs/-prefixed
base would also have read as unprotected.
Closes#65
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`task check` has been red at HEAD since 2bb1fef adopted the canonical
.golangci.yml from infra/ci-templates, which enables govet's `inline` analyzer.
Nothing to do with the change it blocks; reproduced against a clean HEAD before
touching it.
reflect.Ptr is the pre-Go-1.18 spelling of reflect.Pointer and is a deprecated
alias for it. Identical constant, no behaviour change.
Separate commit because it is unrelated to the auth work that found it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gitea host renamed (infra ADR-0004); the mcp-chassis dep already migrated
(3329ff3), so the sequencing gate is clear. `go mod edit -module` + bulk import
rewrite across all .go files. gitea-mcp is a server binary (not an imported
library), so no downstream consumers break. build + task check green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An empty required repo identifier built a trailing-empty path segment
(`/api/v1/repos/{owner}/`) and leaked gitea's bare 404. parseArgs now
validates, via reflection, that `repo`/`name` string args are non-empty and
returns a typed ErrValidation naming the field. Optional identifiers (e.g.
code_search's owner-wide fan-out `repo`) opt out with `,omitempty`. `owner` is
already enforced by the allowlist check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every caller (claude.ai connector, LLMs primed on gitea/GitHub) sends the repo
identifier as `repo`, but the 33 per-repo identifier tools advertised `name`.
The v0.2.8 shim aliased repo->name so it worked, yet the advertised inputSchema
still said `name` — a misleading contract, with the shim load-bearing.
- Flip all 33 identifier tools: schema property + required + struct field/tag
from `name` to `repo`. A compliant `repo` caller now matches the struct
directly; the shim is pure back-compat.
- normalizeAliases is now bidirectional (name<->repo), so legacy `name` callers
still resolve, and repo_create / create_project_from_template — whose `name`
means "name of the NEW repo", not an existing-repo id — keep `name` and still
accept `repo`.
- `number`/`index` left as-is (out of scope; separate pre-existing quirk where
pr_merge advertises `index` rather than `number`).
- Tests: schema-canonical assertion + flipped alias round-trip (explicit `repo`
wins over `name`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-repo MCP tools 404'd while owner-level tools worked (#36). Root cause was
a parameter-name contract mismatch, not a routing fault: every per-repo tool
declares the repo identifier as 'name' (and issue/PR index as 'number'), but
every caller — the claude.ai connector, LLMs primed on gitea's own API — sends
'repo' and 'index'. The unmatched fields zero-valued the upstream path segment,
producing '/api/v1/repos/{owner}//...', which gitea answers with its generic
api-404 whose body points at /api/swagger. That swagger pointer is gitea boiler-
plate, not a misroute — the MCP dispatch was correct all along.
Two layers of defence:
- parseArgs aliases repo->name and index->number (explicit canonical wins;
alias key left intact so pr_merge's real 'index' field is unaffected). Kills
the recurrence by accepting the idiomatic argument names.
- the gitea client rejects any path with an empty segment before the HTTP call,
returning ErrValidation instead of forwarding a malformed path and surfacing
gitea's opaque swagger-404. Kills the silent-misleading-error class.
Closes#36
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>