From c174caa03a64cc95bb8afecdebfc2ef94e2ad96b Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 9 Oct 2026 13:57:17 +0200 Subject: [PATCH 1/3] chore: bump dgageot/rubocop-go dependency Assisted-By: cagent Signed-off-by: David Gageot --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2a912d09e..dc7ac98e1 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/clipperhouse/uax29/v2 v2.7.0 github.com/coder/acp-go-sdk v0.13.5 github.com/creack/pty v1.1.24 - github.com/dgageot/rubocop-go v1.0.1-0.20260925155715-92be797454c8 + github.com/dgageot/rubocop-go v1.0.1-0.20261009115150-202b67b66167 github.com/docker/aijson v0.1.0 github.com/docker/cli v29.8.1+incompatible github.com/docker/go-units v0.5.0 diff --git a/go.sum b/go.sum index 3394efbec..a60c544ca 100644 --- a/go.sum +++ b/go.sum @@ -154,8 +154,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgageot/rubocop-go v1.0.1-0.20260925155715-92be797454c8 h1:tgXLZWkc5PKG38zmUM2+bY0OU8BSQ3v2rr8XdRNd6l8= -github.com/dgageot/rubocop-go v1.0.1-0.20260925155715-92be797454c8/go.mod h1:szP4Puq4DW5fbuTd3EZQH/NsN1VqMHPSI5JcKKZN21Y= +github.com/dgageot/rubocop-go v1.0.1-0.20261009115150-202b67b66167 h1:rdZvdv690da37V6udEtybQNTrJ7eZ8jercvtrlOBLxc= +github.com/dgageot/rubocop-go v1.0.1-0.20261009115150-202b67b66167/go.mod h1:szP4Puq4DW5fbuTd3EZQH/NsN1VqMHPSI5JcKKZN21Y= github.com/dgageot/ultraviolet v0.0.0-20260313154905-9451997d56b6 h1:88fWkkjwzuI4tRTqadbJIbA9O+gO67oyu+2OpHHuuT8= github.com/dgageot/ultraviolet v0.0.0-20260313154905-9451997d56b6/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= From 684458f14536a214ba6ef1e580696a4d5f522368 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 9 Oct 2026 14:11:02 +0200 Subject: [PATCH 2/3] lint: enable the full shared cop catalog Turn on the remaining shared rubocop-go checks (context handling, maps/slices/wait-group modernizations, etc.) across the codebase, add reasoned suppressions for the lifecycle contexts and public constructors that legitimately need them, and update the registration tests and contributing docs to match. Assisted-By: cagent --- cmd/wasm/session.go | 2 + docs/community/contributing/index.md | 44 ++++++- lint/additional_shared_cops_test.go | 182 ++++++++++++++++++++++++++ lint/main.go | 13 ++ lint/shared_cops_test.go | 9 +- pkg/acp/mcp.go | 13 +- pkg/board/app.go | 1 + pkg/board/controller.go | 1 + pkg/board/tmux.go | 1 + pkg/telemetry/genai/embedding.go | 1 + pkg/telemetry/genai/runtime.go | 1 + pkg/telemetry/genai/sandbox.go | 1 + pkg/telemetry/genai/span.go | 1 + pkg/telemetry/mcp/span.go | 1 + pkg/tools/mcp/session_client.go | 3 +- pkg/tools/mcp/session_remote.go | 1 + pkg/tools/startable.go | 5 +- pkg/tui/commands/defaults/commands.go | 2 + pkg/tui/components/sidebar/sidebar.go | 2 + pkg/tui/page/chat/async_input.go | 1 + pkg/tui/page/chat/chat.go | 2 + 21 files changed, 273 insertions(+), 14 deletions(-) create mode 100644 lint/additional_shared_cops_test.go diff --git a/cmd/wasm/session.go b/cmd/wasm/session.go index c26f7e6d5..18b8b8d74 100644 --- a/cmd/wasm/session.go +++ b/cmd/wasm/session.go @@ -27,6 +27,7 @@ type chatSession struct { // ctx outlives individual sends: toolsets started lazily during a turn // (remote MCP) keep using it until close. + //rubocop:disable Lint/NoContextField // session-lifetime context ctx context.Context //nolint:containedctx // session-lifetime context cancel context.CancelFunc @@ -44,6 +45,7 @@ type chatSession struct { // turn is one in-flight send, from start to the end of its event stream. type turn struct { prompt string + //rubocop:disable Lint/NoContextField // turn-scoped cancellation ctx context.Context //nolint:containedctx // turn-scoped cancellation cancel context.CancelFunc done chan struct{} diff --git a/docs/community/contributing/index.md b/docs/community/contributing/index.md index ec8f935ec..fcc6e2b89 100644 --- a/docs/community/contributing/index.md +++ b/docs/community/contributing/index.md @@ -105,10 +105,47 @@ Key conventions: ## Lint rules `task lint` runs the shared and project-specific cops selected in `lint/main.go`. -Reusable checks come from [rubocop-go](https://github.com/dgageot/rubocop-go/blob/92be797454c8ebde41b9f1eb084be3535cfef668/docs/shared-cops.md) +Reusable checks come from [rubocop-go](https://github.com/dgageot/rubocop-go/blob/202b67b66167808e75285d0666f880a2bd4b1144/docs/shared-cops.md) (pinned in `go.mod`); project-specific checks and frozen-config exclusions stay in `lint/`. Cop IDs and `//rubocop:disable` annotations are unchanged. Add shared checks by their -constructors, not by enabling the entire upstream catalog. +constructors, not by enabling the entire upstream catalog. All 36 shared opt-in cops +are currently selected. + +`Lint/ContextFirstParameter`, `Lint/NoContextField`, and +`Lint/HTTPRequestWithContext` enforce context-first operations, explicit context +passing, and contextual HTTP request construction outside frozen config versions. +They inspect production files only; matching is syntactic, so aliases and shadowing +require care. Keep targeted, reasoned suppressions for public constructor signatures +and intentionally owned lifecycle or telemetry contexts. `Lint/NoFatalOutsideMain` +reserves `log.Fatal*` for package main; tests are exempt. + +The following modernization cops inspect resolved production, internal/external +tests, and test-only packages, excluding generated files and frozen configs: + +- `Lint/MapsCopy` and `Lint/MapsClone` suggest plain entry-copy loops and nil-safe + shallow-copy helpers. Preserve merge order, destination initialization, named + types, and nil results; the two cops can report overlapping suggestions. +- `Lint/SlicesContains` and `Lint/SlicesEqual` suggest simple comparable-element + membership and equality helpers. Preserve nil-sensitive guards and comparison + behavior, including NaNs and interface-comparison panics. +- `Lint/SplitSeq` suggests lazy `strings.SplitSeq` or `SplitAfterSeq` for direct + value-only ranges. Preserve input/separator evaluation and empty/trailing fields. +- `Lint/SortedMapKeys` suggests `slices.Sorted(maps.Keys(m))` for adjacent ascending + integer/string key collection. Review nil/empty results and downstream capacity + contracts; this is not an allocation-performance guarantee. +- `Lint/WaitGroupGo` suggests reviewing adjacent `Add(1)`/`go`/`defer Done()`. + The callback must not let a panic escape; preserve captures and completion timing. +- `Lint/ErrorsAsType` suggests fresh targets consumed only on successful matches. + Custom `As` methods can retain target pointers; do not mechanically replace them. +- `Lint/HTTPTestRequestWithContext` suggests immediate test-request context + attachment with constant method/target and nil body. Keep non-nil contexts and + preserve context evaluation versus request validation order. + +These checks follow active build constraints, skip ill-typed candidate packages, +and gate suggestions on the target module/file's Go and stdlib versions, not the +linter toolchain. Suggestions require behavior review, not automatic rewriting. +Rubocop exclusions and suppressions do not configure golangci-lint's overlapping +`modernize` checks. `Lint/FieldsSeq` flags `strings.Fields` slices used only for one value-only range, including loops @@ -151,6 +188,9 @@ Generated files and frozen config versions are excluded. Only constant or local identifier inputs are matched; compound conditions, intervening work, and effectful expressions are excluded. Preserve original values, assignment scope, and evaluation order when introducing the cut result. +Both cut cops also cover immediate `bytes.HasPrefix`/`HasSuffix` plus matching +trimming or slicing on local unnamed byte slices. Preserve nilness, capacity, and +backing-array aliasing. `Lint/NewExpr` flags a fresh local declared only to return its address, recommending `new(expr)` instead. It requires the declaration and `return &x` to be adjacent, the diff --git a/lint/additional_shared_cops_test.go b/lint/additional_shared_cops_test.go new file mode 100644 index 000000000..fb8fac9bf --- /dev/null +++ b/lint/additional_shared_cops_test.go @@ -0,0 +1,182 @@ +package main + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/dgageot/rubocop-go/config" + "github.com/dgageot/rubocop-go/cop" + "github.com/dgageot/rubocop-go/coptest" + "github.com/dgageot/rubocop-go/prog" + "github.com/dgageot/rubocop-go/runner" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAdditionalSharedFileCopScopes(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name, src string + frozen bool + }{ + {"ContextFirstParameter", `import "context" +func f(n int, ctx context.Context) {}`, false}, + {"NoContextField", `import "context" +type service struct { ctx context.Context }`, false}, + {"HTTPRequestWithContext", `import "net/http" +func f() { _, _ = http.NewRequest("GET", "/", nil) }`, false}, + {"NoFatalOutsideMain", `import "log" +func f() { log.Fatal("failed") }`, true}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + c := registeredSharedCop(t, cops, "Lint/"+tc.name) + for _, path := range []string{"pkg/config/v0/sample.go", "pkg/config/v15/sample.go", "pkg/config/latest/sample.go", "notpkg/config/v0/sample.go", "pkg/generated/sample.go", "pkg/sample_test.go"} { + src := "package p\n" + tc.src + if strings.Contains(path, "generated") { + src = "// Code generated by fixture; DO NOT EDIT.\n" + src + } + want := 1 + if strings.HasSuffix(path, "_test.go") || (!tc.frozen && frozenConfigPath.MatchString(path)) { + want = 0 + } + assert.Len(t, coptest.RunNamed(t, c, path, src), want, path) + } + if tc.name == "NoFatalOutsideMain" { + assert.Empty(t, coptest.Run(t, c, "package main\n"+tc.src)) + } + }) + } +} + +func TestAdditionalSharedProgramCopsRunner(t *testing.T) { + for _, tc := range []struct { + name, minimum, older, src string + }{ + {"ErrorsAsType", "1.26", "1.25", `import "errors" +type E struct{} +func (*E) Error() string { return "e" } +func f(err *E) { var e *E; if errors.As(err, &e) { println(e) } }`}, + {"MapsCopy", "1.21", "1.20", `func f(dst, src map[string]int) { for k, v := range src { dst[k] = v } }`}, + {"MapsClone", "1.21", "1.20", `func f(src map[string]int) map[string]int { + if src == nil { return nil }; dst := make(map[string]int, len(src)) + for k, v := range src { dst[k] = v }; return dst +}`}, + {"SlicesContains", "1.21", "1.20", `func f(xs []int, n int) bool { + for _, v := range xs { if v == n { return true } }; return false +}`}, + {"SlicesEqual", "1.21", "1.20", `func f(a, b []int) bool { + if len(a) != len(b) { return false } + for i := range a { if a[i] != b[i] { return false } }; return true +}`}, + {"SplitSeq", "1.24", "1.23", `import "strings" +func f(s string) { for _, part := range strings.Split(s, "/") { println(part) } }`}, + {"SortedMapKeys", "1.23", "1.22", `import "sort" +func f(src map[string]int) []string { + var keys []string; for k := range src { keys = append(keys, k) }; sort.Strings(keys); return keys +}`}, + {"WaitGroupGo", "1.25", "1.24", `import "sync" +func f() { var wg sync.WaitGroup; wg.Add(1); go func() { defer wg.Done(); println("work") }(); wg.Wait() }`}, + {"HTTPTestRequestWithContext", "1.23", "1.22", `import ("context"; "net/http"; "net/http/httptest") +func f(ctx context.Context) *http.Request { return httptest.NewRequest("GET", "/", nil).WithContext(ctx) }`}, + } { + t.Run(tc.name, func(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + id := "Lint/" + tc.name + var want []string + for _, path := range []string{ + "pkg/config/v0/sample.go", "pkg/config/v15/sample.go", "pkg/config/latest/sample.go", + "pkg/config/version/sample.go", "notpkg/config/v0/sample.go", "generated/sample.go", + "internal/sample_test.go", "external/sample_test.go", "testonly/sample_test.go", + "inline/sample.go", "suppressed/sample.go", "inactive/sample.go", "oldlanguage/sample.go", "illtyped/sample_test.go", + } { + src := "package p\n" + tc.src + switch { + case strings.HasPrefix(path, "generated/"): + src = "// Code generated by fixture; DO NOT EDIT.\n" + src + case strings.HasPrefix(path, "external/"): + src = strings.Replace(src, "package p", "package p_test", 1) + case strings.HasPrefix(path, "inline/"): + src = strings.ReplaceAll(src, "\n", " //rubocop:disable "+id+"\n") + src += " //rubocop:disable " + id + "\n" + case strings.HasPrefix(path, "suppressed/"): + src = "//rubocop:disable-file " + id + "\n" + src + case strings.HasPrefix(path, "inactive/"): + src = "//go:build lint_fixture_disabled\n\n" + src + case strings.HasPrefix(path, "oldlanguage/"): + if tc.name != "SplitSeq" { + continue + } + src = "//go:build go1.22\n\n" + src + case strings.HasPrefix(path, "illtyped/"): + src += "\nvar _ = missing\n" + } + require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o700)) + require.NoError(t, os.WriteFile(path, []byte(src), 0o600)) + if strings.HasPrefix(path, "internal/") || strings.HasPrefix(path, "external/") || strings.HasPrefix(path, "illtyped/") { + require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(path), "empty.go"), []byte("package p\n"), 0o600)) + } + if strings.HasPrefix(path, "pkg/config/latest/") || strings.HasPrefix(path, "pkg/config/version/") || strings.HasPrefix(path, "notpkg/") || (strings.HasSuffix(path, "_test.go") && !strings.HasPrefix(path, "illtyped/")) { + want = append(want, path) + } + } + cfg := config.DefaultConfig() + cfg.Cops[id] = config.CopConfig{Severity: "warning"} + c := registeredSharedCop(t, programCops, id) + for _, version := range []string{tc.older, tc.minimum, "1.27"} { + require.NoError(t, os.WriteFile("go.mod", []byte("module example.test\n\ngo "+version+"\n"), 0o600)) + var output bytes.Buffer + r := runner.New(nil, cfg, &output).WithProgramCops([]prog.Cop{c}) + r.Reporter = runner.NewJSONReporter(&output) + count, err := r.Run([]string{"."}) + require.NoError(t, err) + if version == tc.older { + assert.Zero(t, count, output.String()) + continue + } + assert.Equal(t, len(want), count, output.String()) + var report struct { + Offenses []struct{ Cop, File, Severity string } + } + require.NoError(t, json.Unmarshal(output.Bytes(), &report)) + var got []string + for _, offense := range report.Offenses { + assert.Equal(t, id, offense.Cop) + assert.Equal(t, "warning", offense.Severity) + file, err := filepath.Rel(dir, offense.File) + require.NoError(t, err) + got = append(got, filepath.ToSlash(file)) + } + assert.ElementsMatch(t, want, got, version) + } + }) + } +} + +func TestAdditionalSharedFileCopSuppressions(t *testing.T) { + dir := t.TempDir() + t.Chdir(dir) + const src = `package p +import ("context"; "log"; "net/http") +//rubocop:disable Lint/NoContextField // Owns the lifecycle. +type service struct { ctx context.Context } +//rubocop:disable Lint/ContextFirstParameter // Preserve the public signature. +func New(n int, ctx context.Context) {} +func request() { _, _ = http.NewRequest("GET", "/", nil) } //rubocop:disable Lint/HTTPRequestWithContext +func fatal() { log.Fatal("failed") } //rubocop:disable Lint/NoFatalOutsideMain +` + require.NoError(t, os.WriteFile("sample.go", []byte(src), 0o600)) + var selected []cop.Cop + for _, name := range []string{"ContextFirstParameter", "NoContextField", "HTTPRequestWithContext", "NoFatalOutsideMain"} { + selected = append(selected, registeredSharedCop(t, cops, "Lint/"+name)) + } + var output bytes.Buffer + count, err := runner.New(selected, config.DefaultConfig(), &output).Run([]string{"."}) + require.NoError(t, err) + assert.Zero(t, count, output.String()) +} diff --git a/lint/main.go b/lint/main.go index 1a64224df..4d3477474 100644 --- a/lint/main.go +++ b/lint/main.go @@ -30,6 +30,10 @@ var cops = []cop.Cop{ HookConfigSync, HookBuiltinsRegistered, HookBuiltinsDocumented, + rubocops.NewLintContextFirstParameter(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintNoContextField(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintHTTPRequestWithContext(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintNoFatalOutsideMain(), rubocops.NewLintSlogContextual(), ToolArgumentsViaAIJSON, rubocops.NewLintConstructorPurity(), @@ -51,6 +55,15 @@ var cops = []cop.Cop{ // programCops lists whole-program, inter-procedural cops. These run once over // the entire loaded program rather than once per file. var programCops = []prog.Cop{ + rubocops.NewLintErrorsAsType(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintMapsCopy(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintMapsClone(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintSlicesContains(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintSlicesEqual(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintSplitSeq(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintSortedMapKeys(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintWaitGroupGo(cop.WithScope(outsideFrozenConfig)), + rubocops.NewLintHTTPTestRequestWithContext(cop.WithScope(outsideFrozenConfig)), withTypedFiles(rubocops.NewLintSlicesClone, cop.WithScope(outsideFrozenConfig)), rubocops.NewLintSortStableFunc(cop.WithScope(outsideFrozenConfig)), rubocops.NewLintPointerHelper(cop.WithScope(outsideFrozenConfig)), diff --git a/lint/shared_cops_test.go b/lint/shared_cops_test.go index 012040c22..54c53da1f 100644 --- a/lint/shared_cops_test.go +++ b/lint/shared_cops_test.go @@ -22,8 +22,8 @@ import ( func TestSharedCopRegistrations(t *testing.T) { t.Parallel() - require.Len(t, cops, 29) - require.Len(t, programCops, 19) + require.Len(t, cops, 33) + require.Len(t, programCops, 28) counts := make(map[string]int) for _, c := range cops { counts[c.Name()]++ @@ -31,11 +31,12 @@ func TestSharedCopRegistrations(t *testing.T) { for _, c := range programCops { counts[c.Name()]++ } - assert.Len(t, counts, 48) + assert.Len(t, counts, 61) for name, count := range counts { assert.Equal(t, 1, count, name) } for _, name := range []string{ + "ContextFirstParameter", "NoContextField", "HTTPRequestWithContext", "NoFatalOutsideMain", "SlogContextual", "ConstructorPurity", "ConstructorNetworkIO", "WrapErrors", "ErrorStringMatching", "DeferMutexUnlock", "NewExpr", "NoStdoutInLibraries", "ConstructorCommandExec", } { @@ -45,6 +46,8 @@ func TestSharedCopRegistrations(t *testing.T) { "PointerHelper", "ReflectFields", "StdlibUUID", "URLClone", "JSONMarshalWrite", "BenchmarkLoop", "SplitTrimJoin", "FieldsSeq", "StreamCloseSafety", "SlicesClone", "CutPrefix", "CutSuffix", "FieldsSeqLookup", "SortStableFunc", + "ErrorsAsType", "MapsCopy", "MapsClone", "SlicesContains", "SlicesEqual", + "SplitSeq", "SortedMapKeys", "WaitGroupGo", "HTTPTestRequestWithContext", } { registeredSharedCop(t, programCops, "Lint/"+name) } diff --git a/pkg/acp/mcp.go b/pkg/acp/mcp.go index 2dbcd43ce..62f31cf86 100644 --- a/pkg/acp/mcp.go +++ b/pkg/acp/mcp.go @@ -199,12 +199,13 @@ type clientMCPGeneration struct { servers []*mcptools.Toolset instructions string cancel context.CancelFunc - ctx context.Context //nolint:containedctx // owns the generation independently of setup requests - mu sync.Mutex - retired bool - calls sync.WaitGroup - closeOnce sync.Once - closeErr error + //rubocop:disable Lint/NoContextField // owns the generation independently of setup requests + ctx context.Context //nolint:containedctx // owns the generation independently of setup requests + mu sync.Mutex + retired bool + calls sync.WaitGroup + closeOnce sync.Once + closeErr error } func (g *clientMCPGeneration) acquire(ctx context.Context) (context.Context, func(), error) { diff --git a/pkg/board/app.go b/pkg/board/app.go index 98d9994b5..4645812fb 100644 --- a/pkg/board/app.go +++ b/pkg/board/app.go @@ -32,6 +32,7 @@ type Project struct { type App struct { // ctx is the board-lifetime context used by engine operations that // outlive a single UI interaction (git commands, tmux attach). + //rubocop:disable Lint/NoContextField // board-lifetime context ctx context.Context //nolint:containedctx // board-lifetime context store *Store sessions sessionManager diff --git a/pkg/board/controller.go b/pkg/board/controller.go index 37b200852..d021cf51c 100644 --- a/pkg/board/controller.go +++ b/pkg/board/controller.go @@ -53,6 +53,7 @@ type controller struct { // ctx is the board-lifetime context watchers derive from; they are // started lazily (Start) after construction, so it is held here rather // than passed. + //rubocop:disable Lint/NoContextField // base context for background watchers ctx context.Context //nolint:containedctx // base context for background watchers store *Store sessions sessionManager diff --git a/pkg/board/tmux.go b/pkg/board/tmux.go index e46f47fd0..7e815bb17 100644 --- a/pkg/board/tmux.go +++ b/pkg/board/tmux.go @@ -16,6 +16,7 @@ import ( // tmuxSessions manages the tmux sessions the board runs its agents in. type tmuxSessions struct { // ctx is the board-lifetime context tmux commands run under. + //rubocop:disable Lint/NoContextField // sessionManager methods are context-free ctx context.Context //nolint:containedctx // sessionManager methods are context-free } diff --git a/pkg/telemetry/genai/embedding.go b/pkg/telemetry/genai/embedding.go index 9e8c6eb3b..08d83841e 100644 --- a/pkg/telemetry/genai/embedding.go +++ b/pkg/telemetry/genai/embedding.go @@ -36,6 +36,7 @@ type EmbeddingSpan struct { provider string model string startedAt time.Time + //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time mu sync.Mutex diff --git a/pkg/telemetry/genai/runtime.go b/pkg/telemetry/genai/runtime.go index ccd6c5300..f3ba8b04e 100644 --- a/pkg/telemetry/genai/runtime.go +++ b/pkg/telemetry/genai/runtime.go @@ -318,6 +318,7 @@ type CacheSpan struct { // produce span-context exemplars (drill Mimir bucket → Tempo // trace). Without this the counter measurement gets only the // resource attributes. + //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time backing string op string diff --git a/pkg/telemetry/genai/sandbox.go b/pkg/telemetry/genai/sandbox.go index 4b97d7fc0..e54578c53 100644 --- a/pkg/telemetry/genai/sandbox.go +++ b/pkg/telemetry/genai/sandbox.go @@ -85,6 +85,7 @@ type SandboxSpan struct { span trace.Span // metricCtx carries the active span context so histogram Record // calls produce span-context exemplars (drill Mimir → Tempo). + //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time startedAt time.Time runtime string diff --git a/pkg/telemetry/genai/span.go b/pkg/telemetry/genai/span.go index 3c64bda6d..34c7ef1d3 100644 --- a/pkg/telemetry/genai/span.go +++ b/pkg/telemetry/genai/span.go @@ -88,6 +88,7 @@ type ChatSpan struct { // trace-to-metric exemplar link. Using context.Background() here // would silently strip the active span context and break // drill-from-metric-bucket-to-trace navigation in Tempo/Mimir. + //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time mu sync.Mutex diff --git a/pkg/telemetry/mcp/span.go b/pkg/telemetry/mcp/span.go index 594ba99bd..13214c74d 100644 --- a/pkg/telemetry/mcp/span.go +++ b/pkg/telemetry/mcp/span.go @@ -62,6 +62,7 @@ type Span struct { // metricCtx carries the active span context so the duration // histogram measurement produces span-context exemplars (drill // Mimir bucket → Tempo trace). + //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time startedAt time.Time method string diff --git a/pkg/tools/mcp/session_client.go b/pkg/tools/mcp/session_client.go index 3f404149d..51e36652f 100644 --- a/pkg/tools/mcp/session_client.go +++ b/pkg/tools/mcp/session_client.go @@ -43,7 +43,8 @@ type sessionClient struct { } type inflightCall struct { - scope tools.HandlerScope + scope tools.HandlerScope + //rubocop:disable Lint/NoContextField // bounded by CallTool; canceled when unregistered ctx context.Context //nolint:containedctx // bounded by CallTool; canceled when unregistered cancel context.CancelFunc } diff --git a/pkg/tools/mcp/session_remote.go b/pkg/tools/mcp/session_remote.go index d10dc3f07..abe8599f7 100644 --- a/pkg/tools/mcp/session_remote.go +++ b/pkg/tools/mcp/session_remote.go @@ -162,6 +162,7 @@ func sessionRemoteError(err error) error { } type sessionRemoteHTTP struct { + //rubocop:disable Lint/NoContextField // owned by one connection, canceled and drained on close ctx context.Context //nolint:containedctx // owned by one connection, canceled and drained on close endpoint string headers map[string]string diff --git a/pkg/tools/startable.go b/pkg/tools/startable.go index e33e25311..2a3a59b26 100644 --- a/pkg/tools/startable.go +++ b/pkg/tools/startable.go @@ -286,8 +286,9 @@ type StartableToolSet struct { // start a later caller deliberately performs. Lock order: mu may be // held when taking stopRequestMu; stopRequestMu is never held while // acquiring mu. - stopRequestMu sync.Mutex - stopRequested bool + stopRequestMu sync.Mutex + stopRequested bool + //rubocop:disable Lint/NoContextField // carries the requester's values into a late stop; set iff stopRequested stopRequestCtx context.Context //nolint:containedctx // carries the requester's values into a late stop; set iff stopRequested startStreak failureStreak // Start() failures diff --git a/pkg/tui/commands/defaults/commands.go b/pkg/tui/commands/defaults/commands.go index e1408ad54..864c3e222 100644 --- a/pkg/tui/commands/defaults/commands.go +++ b/pkg/tui/commands/defaults/commands.go @@ -654,6 +654,8 @@ func effortCandidates(ctx context.Context, source effortLevelsSource) []commands // builtInSessionCommands) so that function stays free of any // effort-levels-source dependency. ctx is the long-lived TUI context, closed // over by the completer so it can re-resolve the model on every call. +// +//rubocop:disable Lint/ContextFirstParameter // Keep items-first ordering consistent with adjacent completion helpers. func attachEffortCompletion(items []commands.Item, ctx context.Context, source effortLevelsSource) { for i := range items { if items[i].ID != "session.effort" { diff --git a/pkg/tui/components/sidebar/sidebar.go b/pkg/tui/components/sidebar/sidebar.go index ec2e13412..0b5b94463 100644 --- a/pkg/tui/components/sidebar/sidebar.go +++ b/pkg/tui/components/sidebar/sidebar.go @@ -423,6 +423,8 @@ type model struct { } // New creates a new sidebar bound to the given session state. +// +//rubocop:disable Lint/ContextFirstParameter // Preserve the public constructor signature. func New(ar *animation.Runtime, ctx context.Context, sessionState *service.SessionState) Model { ti := textinput.New() ti.Placeholder = "Session title" diff --git a/pkg/tui/page/chat/async_input.go b/pkg/tui/page/chat/async_input.go index 925ea7f8f..09990c090 100644 --- a/pkg/tui/page/chat/async_input.go +++ b/pkg/tui/page/chat/async_input.go @@ -13,6 +13,7 @@ import ( // inputScope identifies one page lifetime, including a reload of the same conversation. type inputScope struct { + //rubocop:disable Lint/NoContextField // Owns the lifetime of in-flight page submissions. ctx context.Context //nolint:containedctx // Owns the lifetime of in-flight page submissions. cancel context.CancelFunc app *app.App diff --git a/pkg/tui/page/chat/chat.go b/pkg/tui/page/chat/chat.go index 76683bd25..5c1d0408e 100644 --- a/pkg/tui/page/chat/chat.go +++ b/pkg/tui/page/chat/chat.go @@ -398,6 +398,8 @@ func defaultKeyMap() KeyMap { } // New creates a new chat page +// +//rubocop:disable Lint/ContextFirstParameter // Preserve the public constructor signature. func New(ar *animation.Runtime, ctx context.Context, a *app.App, sessionState *service.SessionState, opts ...PageOption) Page { pageCtx, cancel := context.WithCancel(ctx) registry := tooldefaults.NewRegistry() From 395dcff61f68863996c21dc99e43de2d962347ee Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 9 Oct 2026 14:16:23 +0200 Subject: [PATCH 3/3] lint: drop ContextFirstParameter and NoContextField suppressions --- cmd/wasm/session.go | 2 -- docs/community/contributing/index.md | 20 +++++++-------- lint/additional_shared_cops_test.go | 35 +-------------------------- lint/main.go | 2 -- lint/shared_cops_test.go | 9 ++++--- pkg/acp/mcp.go | 13 +++++----- pkg/board/app.go | 1 - pkg/board/controller.go | 1 - pkg/board/tmux.go | 1 - pkg/telemetry/genai/embedding.go | 1 - pkg/telemetry/genai/runtime.go | 1 - pkg/telemetry/genai/sandbox.go | 1 - pkg/telemetry/genai/span.go | 1 - pkg/telemetry/mcp/span.go | 1 - pkg/tools/mcp/session_client.go | 3 +-- pkg/tools/mcp/session_remote.go | 1 - pkg/tools/startable.go | 5 ++-- pkg/tui/commands/defaults/commands.go | 2 -- pkg/tui/components/sidebar/sidebar.go | 2 -- pkg/tui/page/chat/async_input.go | 1 - pkg/tui/page/chat/chat.go | 2 -- 21 files changed, 26 insertions(+), 79 deletions(-) diff --git a/cmd/wasm/session.go b/cmd/wasm/session.go index 18b8b8d74..c26f7e6d5 100644 --- a/cmd/wasm/session.go +++ b/cmd/wasm/session.go @@ -27,7 +27,6 @@ type chatSession struct { // ctx outlives individual sends: toolsets started lazily during a turn // (remote MCP) keep using it until close. - //rubocop:disable Lint/NoContextField // session-lifetime context ctx context.Context //nolint:containedctx // session-lifetime context cancel context.CancelFunc @@ -45,7 +44,6 @@ type chatSession struct { // turn is one in-flight send, from start to the end of its event stream. type turn struct { prompt string - //rubocop:disable Lint/NoContextField // turn-scoped cancellation ctx context.Context //nolint:containedctx // turn-scoped cancellation cancel context.CancelFunc done chan struct{} diff --git a/docs/community/contributing/index.md b/docs/community/contributing/index.md index fcc6e2b89..8d1070305 100644 --- a/docs/community/contributing/index.md +++ b/docs/community/contributing/index.md @@ -108,16 +108,16 @@ Key conventions: Reusable checks come from [rubocop-go](https://github.com/dgageot/rubocop-go/blob/202b67b66167808e75285d0666f880a2bd4b1144/docs/shared-cops.md) (pinned in `go.mod`); project-specific checks and frozen-config exclusions stay in `lint/`. Cop IDs and `//rubocop:disable` annotations are unchanged. Add shared checks by their -constructors, not by enabling the entire upstream catalog. All 36 shared opt-in cops -are currently selected. - -`Lint/ContextFirstParameter`, `Lint/NoContextField`, and -`Lint/HTTPRequestWithContext` enforce context-first operations, explicit context -passing, and contextual HTTP request construction outside frozen config versions. -They inspect production files only; matching is syntactic, so aliases and shadowing -require care. Keep targeted, reasoned suppressions for public constructor signatures -and intentionally owned lifecycle or telemetry contexts. `Lint/NoFatalOutsideMain` -reserves `log.Fatal*` for package main; tests are exempt. +constructors, not by enabling the entire upstream catalog. 34 of the 36 shared +opt-in cops are currently selected. `Lint/ContextFirstParameter` and +`Lint/NoContextField` remain disabled: existing public constructor signatures and +intentionally owned lifecycle or telemetry contexts would require suppressions or +unrelated refactoring. + +`Lint/HTTPRequestWithContext` enforces contextual HTTP request construction outside +frozen config versions. It inspects production files only; matching is syntactic, +so aliases and shadowing require care. `Lint/NoFatalOutsideMain` reserves +`log.Fatal*` for package main; tests are exempt. The following modernization cops inspect resolved production, internal/external tests, and test-only packages, excluding generated files and frozen configs: diff --git a/lint/additional_shared_cops_test.go b/lint/additional_shared_cops_test.go index fb8fac9bf..e8f02c551 100644 --- a/lint/additional_shared_cops_test.go +++ b/lint/additional_shared_cops_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/dgageot/rubocop-go/config" - "github.com/dgageot/rubocop-go/cop" "github.com/dgageot/rubocop-go/coptest" "github.com/dgageot/rubocop-go/prog" "github.com/dgageot/rubocop-go/runner" @@ -23,10 +22,6 @@ func TestAdditionalSharedFileCopScopes(t *testing.T) { name, src string frozen bool }{ - {"ContextFirstParameter", `import "context" -func f(n int, ctx context.Context) {}`, false}, - {"NoContextField", `import "context" -type service struct { ctx context.Context }`, false}, {"HTTPRequestWithContext", `import "net/http" func f() { _, _ = http.NewRequest("GET", "/", nil) }`, false}, {"NoFatalOutsideMain", `import "log" @@ -93,7 +88,7 @@ func f(ctx context.Context) *http.Request { return httptest.NewRequest("GET", "/ "pkg/config/v0/sample.go", "pkg/config/v15/sample.go", "pkg/config/latest/sample.go", "pkg/config/version/sample.go", "notpkg/config/v0/sample.go", "generated/sample.go", "internal/sample_test.go", "external/sample_test.go", "testonly/sample_test.go", - "inline/sample.go", "suppressed/sample.go", "inactive/sample.go", "oldlanguage/sample.go", "illtyped/sample_test.go", + "inactive/sample.go", "oldlanguage/sample.go", "illtyped/sample_test.go", } { src := "package p\n" + tc.src switch { @@ -101,11 +96,6 @@ func f(ctx context.Context) *http.Request { return httptest.NewRequest("GET", "/ src = "// Code generated by fixture; DO NOT EDIT.\n" + src case strings.HasPrefix(path, "external/"): src = strings.Replace(src, "package p", "package p_test", 1) - case strings.HasPrefix(path, "inline/"): - src = strings.ReplaceAll(src, "\n", " //rubocop:disable "+id+"\n") - src += " //rubocop:disable " + id + "\n" - case strings.HasPrefix(path, "suppressed/"): - src = "//rubocop:disable-file " + id + "\n" + src case strings.HasPrefix(path, "inactive/"): src = "//go:build lint_fixture_disabled\n\n" + src case strings.HasPrefix(path, "oldlanguage/"): @@ -157,26 +147,3 @@ func f(ctx context.Context) *http.Request { return httptest.NewRequest("GET", "/ }) } } - -func TestAdditionalSharedFileCopSuppressions(t *testing.T) { - dir := t.TempDir() - t.Chdir(dir) - const src = `package p -import ("context"; "log"; "net/http") -//rubocop:disable Lint/NoContextField // Owns the lifecycle. -type service struct { ctx context.Context } -//rubocop:disable Lint/ContextFirstParameter // Preserve the public signature. -func New(n int, ctx context.Context) {} -func request() { _, _ = http.NewRequest("GET", "/", nil) } //rubocop:disable Lint/HTTPRequestWithContext -func fatal() { log.Fatal("failed") } //rubocop:disable Lint/NoFatalOutsideMain -` - require.NoError(t, os.WriteFile("sample.go", []byte(src), 0o600)) - var selected []cop.Cop - for _, name := range []string{"ContextFirstParameter", "NoContextField", "HTTPRequestWithContext", "NoFatalOutsideMain"} { - selected = append(selected, registeredSharedCop(t, cops, "Lint/"+name)) - } - var output bytes.Buffer - count, err := runner.New(selected, config.DefaultConfig(), &output).Run([]string{"."}) - require.NoError(t, err) - assert.Zero(t, count, output.String()) -} diff --git a/lint/main.go b/lint/main.go index 4d3477474..dc9fa844e 100644 --- a/lint/main.go +++ b/lint/main.go @@ -30,8 +30,6 @@ var cops = []cop.Cop{ HookConfigSync, HookBuiltinsRegistered, HookBuiltinsDocumented, - rubocops.NewLintContextFirstParameter(cop.WithScope(outsideFrozenConfig)), - rubocops.NewLintNoContextField(cop.WithScope(outsideFrozenConfig)), rubocops.NewLintHTTPRequestWithContext(cop.WithScope(outsideFrozenConfig)), rubocops.NewLintNoFatalOutsideMain(), rubocops.NewLintSlogContextual(), diff --git a/lint/shared_cops_test.go b/lint/shared_cops_test.go index 54c53da1f..edf034062 100644 --- a/lint/shared_cops_test.go +++ b/lint/shared_cops_test.go @@ -22,7 +22,7 @@ import ( func TestSharedCopRegistrations(t *testing.T) { t.Parallel() - require.Len(t, cops, 33) + require.Len(t, cops, 31) require.Len(t, programCops, 28) counts := make(map[string]int) for _, c := range cops { @@ -31,12 +31,15 @@ func TestSharedCopRegistrations(t *testing.T) { for _, c := range programCops { counts[c.Name()]++ } - assert.Len(t, counts, 61) + assert.Len(t, counts, 59) + for _, name := range []string{"Lint/ContextFirstParameter", "Lint/NoContextField"} { + assert.NotContains(t, counts, name) + } for name, count := range counts { assert.Equal(t, 1, count, name) } for _, name := range []string{ - "ContextFirstParameter", "NoContextField", "HTTPRequestWithContext", "NoFatalOutsideMain", + "HTTPRequestWithContext", "NoFatalOutsideMain", "SlogContextual", "ConstructorPurity", "ConstructorNetworkIO", "WrapErrors", "ErrorStringMatching", "DeferMutexUnlock", "NewExpr", "NoStdoutInLibraries", "ConstructorCommandExec", } { diff --git a/pkg/acp/mcp.go b/pkg/acp/mcp.go index 62f31cf86..2dbcd43ce 100644 --- a/pkg/acp/mcp.go +++ b/pkg/acp/mcp.go @@ -199,13 +199,12 @@ type clientMCPGeneration struct { servers []*mcptools.Toolset instructions string cancel context.CancelFunc - //rubocop:disable Lint/NoContextField // owns the generation independently of setup requests - ctx context.Context //nolint:containedctx // owns the generation independently of setup requests - mu sync.Mutex - retired bool - calls sync.WaitGroup - closeOnce sync.Once - closeErr error + ctx context.Context //nolint:containedctx // owns the generation independently of setup requests + mu sync.Mutex + retired bool + calls sync.WaitGroup + closeOnce sync.Once + closeErr error } func (g *clientMCPGeneration) acquire(ctx context.Context) (context.Context, func(), error) { diff --git a/pkg/board/app.go b/pkg/board/app.go index 4645812fb..98d9994b5 100644 --- a/pkg/board/app.go +++ b/pkg/board/app.go @@ -32,7 +32,6 @@ type Project struct { type App struct { // ctx is the board-lifetime context used by engine operations that // outlive a single UI interaction (git commands, tmux attach). - //rubocop:disable Lint/NoContextField // board-lifetime context ctx context.Context //nolint:containedctx // board-lifetime context store *Store sessions sessionManager diff --git a/pkg/board/controller.go b/pkg/board/controller.go index d021cf51c..37b200852 100644 --- a/pkg/board/controller.go +++ b/pkg/board/controller.go @@ -53,7 +53,6 @@ type controller struct { // ctx is the board-lifetime context watchers derive from; they are // started lazily (Start) after construction, so it is held here rather // than passed. - //rubocop:disable Lint/NoContextField // base context for background watchers ctx context.Context //nolint:containedctx // base context for background watchers store *Store sessions sessionManager diff --git a/pkg/board/tmux.go b/pkg/board/tmux.go index 7e815bb17..e46f47fd0 100644 --- a/pkg/board/tmux.go +++ b/pkg/board/tmux.go @@ -16,7 +16,6 @@ import ( // tmuxSessions manages the tmux sessions the board runs its agents in. type tmuxSessions struct { // ctx is the board-lifetime context tmux commands run under. - //rubocop:disable Lint/NoContextField // sessionManager methods are context-free ctx context.Context //nolint:containedctx // sessionManager methods are context-free } diff --git a/pkg/telemetry/genai/embedding.go b/pkg/telemetry/genai/embedding.go index 08d83841e..9e8c6eb3b 100644 --- a/pkg/telemetry/genai/embedding.go +++ b/pkg/telemetry/genai/embedding.go @@ -36,7 +36,6 @@ type EmbeddingSpan struct { provider string model string startedAt time.Time - //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time mu sync.Mutex diff --git a/pkg/telemetry/genai/runtime.go b/pkg/telemetry/genai/runtime.go index f3ba8b04e..ccd6c5300 100644 --- a/pkg/telemetry/genai/runtime.go +++ b/pkg/telemetry/genai/runtime.go @@ -318,7 +318,6 @@ type CacheSpan struct { // produce span-context exemplars (drill Mimir bucket → Tempo // trace). Without this the counter measurement gets only the // resource attributes. - //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time backing string op string diff --git a/pkg/telemetry/genai/sandbox.go b/pkg/telemetry/genai/sandbox.go index e54578c53..4b97d7fc0 100644 --- a/pkg/telemetry/genai/sandbox.go +++ b/pkg/telemetry/genai/sandbox.go @@ -85,7 +85,6 @@ type SandboxSpan struct { span trace.Span // metricCtx carries the active span context so histogram Record // calls produce span-context exemplars (drill Mimir → Tempo). - //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time startedAt time.Time runtime string diff --git a/pkg/telemetry/genai/span.go b/pkg/telemetry/genai/span.go index 34c7ef1d3..3c64bda6d 100644 --- a/pkg/telemetry/genai/span.go +++ b/pkg/telemetry/genai/span.go @@ -88,7 +88,6 @@ type ChatSpan struct { // trace-to-metric exemplar link. Using context.Background() here // would silently strip the active span context and break // drill-from-metric-bucket-to-trace navigation in Tempo/Mimir. - //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time mu sync.Mutex diff --git a/pkg/telemetry/mcp/span.go b/pkg/telemetry/mcp/span.go index 13214c74d..594ba99bd 100644 --- a/pkg/telemetry/mcp/span.go +++ b/pkg/telemetry/mcp/span.go @@ -62,7 +62,6 @@ type Span struct { // metricCtx carries the active span context so the duration // histogram measurement produces span-context exemplars (drill // Mimir bucket → Tempo trace). - //rubocop:disable Lint/NoContextField // intentional: needed for OTel exemplar attribution at End time metricCtx context.Context //nolint:containedctx // intentional: needed for OTel exemplar attribution at End time startedAt time.Time method string diff --git a/pkg/tools/mcp/session_client.go b/pkg/tools/mcp/session_client.go index 51e36652f..3f404149d 100644 --- a/pkg/tools/mcp/session_client.go +++ b/pkg/tools/mcp/session_client.go @@ -43,8 +43,7 @@ type sessionClient struct { } type inflightCall struct { - scope tools.HandlerScope - //rubocop:disable Lint/NoContextField // bounded by CallTool; canceled when unregistered + scope tools.HandlerScope ctx context.Context //nolint:containedctx // bounded by CallTool; canceled when unregistered cancel context.CancelFunc } diff --git a/pkg/tools/mcp/session_remote.go b/pkg/tools/mcp/session_remote.go index abe8599f7..d10dc3f07 100644 --- a/pkg/tools/mcp/session_remote.go +++ b/pkg/tools/mcp/session_remote.go @@ -162,7 +162,6 @@ func sessionRemoteError(err error) error { } type sessionRemoteHTTP struct { - //rubocop:disable Lint/NoContextField // owned by one connection, canceled and drained on close ctx context.Context //nolint:containedctx // owned by one connection, canceled and drained on close endpoint string headers map[string]string diff --git a/pkg/tools/startable.go b/pkg/tools/startable.go index 2a3a59b26..e33e25311 100644 --- a/pkg/tools/startable.go +++ b/pkg/tools/startable.go @@ -286,9 +286,8 @@ type StartableToolSet struct { // start a later caller deliberately performs. Lock order: mu may be // held when taking stopRequestMu; stopRequestMu is never held while // acquiring mu. - stopRequestMu sync.Mutex - stopRequested bool - //rubocop:disable Lint/NoContextField // carries the requester's values into a late stop; set iff stopRequested + stopRequestMu sync.Mutex + stopRequested bool stopRequestCtx context.Context //nolint:containedctx // carries the requester's values into a late stop; set iff stopRequested startStreak failureStreak // Start() failures diff --git a/pkg/tui/commands/defaults/commands.go b/pkg/tui/commands/defaults/commands.go index 864c3e222..e1408ad54 100644 --- a/pkg/tui/commands/defaults/commands.go +++ b/pkg/tui/commands/defaults/commands.go @@ -654,8 +654,6 @@ func effortCandidates(ctx context.Context, source effortLevelsSource) []commands // builtInSessionCommands) so that function stays free of any // effort-levels-source dependency. ctx is the long-lived TUI context, closed // over by the completer so it can re-resolve the model on every call. -// -//rubocop:disable Lint/ContextFirstParameter // Keep items-first ordering consistent with adjacent completion helpers. func attachEffortCompletion(items []commands.Item, ctx context.Context, source effortLevelsSource) { for i := range items { if items[i].ID != "session.effort" { diff --git a/pkg/tui/components/sidebar/sidebar.go b/pkg/tui/components/sidebar/sidebar.go index 0b5b94463..ec2e13412 100644 --- a/pkg/tui/components/sidebar/sidebar.go +++ b/pkg/tui/components/sidebar/sidebar.go @@ -423,8 +423,6 @@ type model struct { } // New creates a new sidebar bound to the given session state. -// -//rubocop:disable Lint/ContextFirstParameter // Preserve the public constructor signature. func New(ar *animation.Runtime, ctx context.Context, sessionState *service.SessionState) Model { ti := textinput.New() ti.Placeholder = "Session title" diff --git a/pkg/tui/page/chat/async_input.go b/pkg/tui/page/chat/async_input.go index 09990c090..925ea7f8f 100644 --- a/pkg/tui/page/chat/async_input.go +++ b/pkg/tui/page/chat/async_input.go @@ -13,7 +13,6 @@ import ( // inputScope identifies one page lifetime, including a reload of the same conversation. type inputScope struct { - //rubocop:disable Lint/NoContextField // Owns the lifetime of in-flight page submissions. ctx context.Context //nolint:containedctx // Owns the lifetime of in-flight page submissions. cancel context.CancelFunc app *app.App diff --git a/pkg/tui/page/chat/chat.go b/pkg/tui/page/chat/chat.go index 5c1d0408e..76683bd25 100644 --- a/pkg/tui/page/chat/chat.go +++ b/pkg/tui/page/chat/chat.go @@ -398,8 +398,6 @@ func defaultKeyMap() KeyMap { } // New creates a new chat page -// -//rubocop:disable Lint/ContextFirstParameter // Preserve the public constructor signature. func New(ar *animation.Runtime, ctx context.Context, a *app.App, sessionState *service.SessionState, opts ...PageOption) Page { pageCtx, cancel := context.WithCancel(ctx) registry := tooldefaults.NewRegistry()