Compare commits

..
2 Commits
Author SHA1 Message Date
mathias 2bb1fef543 chore(lint): adopt the canonical .golangci.yml from infra/ci-templates
CD / Lint / Test / Vet (push) Successful in 10s
CD / Build & Import (push) Successful in 5s
CD / Deploy via GitOps (push) Successful in 4s
Canonical lives at infra ci-templates/golangci.yml, distributed by the
infra#309 pattern: byte-identical copies, checksum drift gate in infra. Do
not edit this file here -- edit the canonical and run `task ci:sync`.

Verified locally before pushing, not pushed-and-hoped: golangci-lint v2.11.4
against a Go 1.26.1 toolchain (matching CI) reports 0 issues here.

Also applies gofumpt, which this config enables and the golangci-lint v2
default set does not check at all. That gap is why the drift was invisible:
earlier passes ran gofmt, which is weaker. Mechanical, deterministic,
build+vet verified.
2026-09-05 14:42:59 +02:00
mathias ee9ac99c40 style: apply gofmt
CD / Lint / Test / Vet (push) Successful in 10s
CD / Build & Import (push) Successful in 4s
CD / Deploy via GitOps (push) Successful in 4s
Estate-wide consolidation pass. golangci-lint v2 default set enables NO
formatter, so gofmt drift accumulated silently across the estate -- 8 repos
were carrying it, this one included. Same hole that let two files rot in
dma-cli until they were found by hand (dma-cli#12).

Mechanical and deterministic: `gofmt -w`, nothing hand-edited. Verified
before commit -- gofmt -l now reports 0, go build and go vet both pass.

Clears this repo to adopt the canonical .golangci.yml (infra
ci-templates/golangci.yml, infra#309 pattern) in a later wave.
2026-09-05 12:40:34 +02:00
9 changed files with 70 additions and 16 deletions
+52
View File
@@ -0,0 +1,52 @@
# Canonical golangci-lint configuration for the estate's Go repos (infra#309
# pattern: one canonical file here, a byte-identical copy in each adopter,
# and the drift gate in THIS repo comparing sha256).
#
# Why this exists: 26 Go repos, 3 had a lint config, and all three were
# different. The 23 without one ran golangci-lint's v2 default set, which
# does NOT include a formatter -- so gofmt/gofumpt drift went uncaught until
# it was found by hand (dma-cli#12: two files drifted, nothing flagged them).
#
# The two configs this replaces (cobalt-dingo, coo-agent) were near-identical
# copies of each other AND both invalid under v2: they carried v1-style
# `linters-settings` and `issues.exclude-rules` keys, which `golangci-lint
# config verify` rejects outright. Their revive settings and test exclusions
# were therefore not in effect. Consolidating fixes that too.
#
# Per-repo variation does NOT belong here -- this file must stay byte-identical
# across adopters so the gate is a checksum, not a diff a human has to judge.
# A repo with a genuine exception (agentsquad excludes its seeded-bug fixture
# tree, which exists precisely to contain broken code) keeps its own config and
# is not listed as an adopter.
version: "2"
linters:
default: none
enable:
- errcheck # unchecked errors
- govet # suspicious constructs
- staticcheck # broad static analysis
- unused # unused code
- misspell # spelling
- revive # idiomatic Go
- exhaustive # exhaustive switch on enums
settings:
revive:
rules:
- name: var-naming
severity: warning
exclusions:
rules:
# Test files: table-driven cases legitimately carry unused struct fields
# and long functions.
- path: _test\.go
linters:
- revive
- unused
formatters:
enable:
- gofumpt
+2 -2
View File
@@ -27,8 +27,8 @@ func TestEmptyPathSegmentRejectedBeforeNetwork(t *testing.T) {
c := gitea.NewClient(srv.URL, "tok") c := gitea.NewClient(srv.URL, "tok")
paths := []string{ paths := []string{
"/api/v1/repos/mathias//issues", // empty repo "/api/v1/repos/mathias//issues", // empty repo
"/api/v1/repos//gitea-mcp/contents/", // empty owner "/api/v1/repos//gitea-mcp/contents/", // empty owner
"/api/v1/repos/mathias//issues?state=open", // empty repo before query "/api/v1/repos/mathias//issues?state=open", // empty repo before query
} }
for _, p := range paths { for _, p := range paths {
-1
View File
@@ -255,4 +255,3 @@ func (c *Client) GetRepo(ctx context.Context, owner, name string) (*Repo, error)
} }
return &r, nil return &r, nil
} }
+2 -2
View File
@@ -79,8 +79,8 @@ func TestCreateRepo_Org(t *testing.T) {
c := gitea.NewClient(srv.URL, "tok") c := gitea.NewClient(srv.URL, "tok")
r, err := c.CreateRepo(context.Background(), gitea.CreateRepoArgs{ r, err := c.CreateRepo(context.Background(), gitea.CreateRepoArgs{
Name: "infra", Name: "infra",
Org: "hyperguild", Org: "hyperguild",
}) })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "hyperguild/infra", r.FullName) assert.Equal(t, "hyperguild/infra", r.FullName)
+1 -1
View File
@@ -9,7 +9,7 @@ import (
) )
const ( const (
ProtocolVersion = "2025-03-26" ProtocolVersion = "2025-03-26"
maxRequestBodyBytes = 1 << 20 // 1 MiB maxRequestBodyBytes = 1 << 20 // 1 MiB
) )
+1
View File
@@ -25,6 +25,7 @@ type echoTool struct{}
func (echoTool) Descriptor() registry.ToolDescriptor { func (echoTool) Descriptor() registry.ToolDescriptor {
return registry.ToolDescriptor{Name: "echo", Description: "echoes its input"} return registry.ToolDescriptor{Name: "echo", Description: "echoes its input"}
} }
func (echoTool) Call(_ context.Context, args json.RawMessage) (json.RawMessage, error) { func (echoTool) Call(_ context.Context, args json.RawMessage) (json.RawMessage, error) {
return args, nil return args, nil
} }
+6 -4
View File
@@ -16,10 +16,12 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
const branchCheckExistsResp = `{"name":"feat/x","commit":{"id":"abc","url":"http://example.com"}}` const (
const createBranchResp = `{"name":"feat/new","commit":{"id":"abc","url":"http://example.com"}}` branchCheckExistsResp = `{"name":"feat/x","commit":{"id":"abc","url":"http://example.com"}}`
const upsertFileResp = `{"content":{"path":"doc.md","sha":"filsha","html_url":"http://example.com/doc.md"},"commit":{"sha":"cmt1","html_url":"http://example.com/commit/cmt1"}}` createBranchResp = `{"name":"feat/new","commit":{"id":"abc","url":"http://example.com"}}`
const getRepoResp = `{"name":"myrepo","full_name":"owner/myrepo","default_branch":"main"}` upsertFileResp = `{"content":{"path":"doc.md","sha":"filsha","html_url":"http://example.com/doc.md"},"commit":{"sha":"cmt1","html_url":"http://example.com/commit/cmt1"}}`
getRepoResp = `{"name":"myrepo","full_name":"owner/myrepo","default_branch":"main"}`
)
func TestFileWriteBranchCreatesBranchAndFile(t *testing.T) { func TestFileWriteBranchCreatesBranchAndFile(t *testing.T) {
mux := http.NewServeMux() mux := http.NewServeMux()
+1 -1
View File
@@ -36,7 +36,7 @@ const (
func (t *TBDShip) Descriptor() registry.ToolDescriptor { func (t *TBDShip) Descriptor() registry.ToolDescriptor {
return registry.ToolDescriptor{ return registry.ToolDescriptor{
Name: "tbd_ship", Name: "tbd_ship",
Description: "Trunk-based ship of a single-file change: branch from base, write the file, open a PR, and auto-merge (squash) to base ONLY when the head commit's CI is fully green. Fails closed to PR-only (never merges) when CI is pending, red, or absent, when the base branch requires reviews, or when the merge isn't clean — returning the PR for manual handling with a reason. Set ci_timeout_seconds to poll CI to completion (default 0 = snapshot, which returns pending right after opening the PR). Returns {merged, pr_url, pr_number, ci_status, reason, branch}.", Description: "Trunk-based ship of a single-file change: branch from base, write the file, open a PR, and auto-merge (squash) to base ONLY when the head commit's CI is fully green. Fails closed to PR-only (never merges) when CI is pending, red, or absent, when the base branch requires reviews, or when the merge isn't clean — returning the PR for manual handling with a reason. Set ci_timeout_seconds to poll CI to completion (default 0 = snapshot, which returns pending right after opening the PR). Returns {merged, pr_url, pr_number, ci_status, reason, branch}.",
InputSchema: json.RawMessage(`{ InputSchema: json.RawMessage(`{
"type":"object", "type":"object",
+5 -5
View File
@@ -52,11 +52,11 @@ func TestWorkflowRunListTool(t *testing.T) {
}, },
}, },
{ {
name: "status=all is no-op", name: "status=all is no-op",
input: `{"owner":"mathias","name":"gitea-mcp","status":"all"}`, input: `{"owner":"mathias","name":"gitea-mcp","status":"all"}`,
notQuery: []string{"status"}, notQuery: []string{"status"},
respBody: `{"total_count":0,"workflow_runs":[]}`, respBody: `{"total_count":0,"workflow_runs":[]}`,
assert: func(t *testing.T, out string) {}, assert: func(t *testing.T, out string) {},
}, },
{ {
name: "branch filter", name: "branch filter",