From 818f36edd00a49f568913de778e11d99757f99c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 02:05:22 +0000 Subject: [PATCH 1/2] Add golangci-lint configuration and CI workflow Set up golangci-lint v2 with the standard linter set (errcheck, govet, ineffassign, staticcheck, unused) plus gocritic, misspell, unconvert, and goimports. Add a GitHub Actions workflow that runs the linter on PRs and pushes to main. Fix all existing lint issues: remove unused valueString function, correct goimports formatting, and exclude deferred Close() calls from errcheck. https://claude.ai/code/session_01WQDT6GkU9KZX57VdE5jZBi --- .github/workflows/lint.yml | 29 +++++++++++++++++++++ .golangci.yml | 34 +++++++++++++++++++++++++ AGENTS.md | 7 ++++- internal/adapter/codex/adapter.go | 4 --- internal/adapter/hermes/adapter_test.go | 28 ++++++++++---------- internal/bundle/sqlite/bundle.go | 8 +++--- pkg/tracecc/tracecc.go | 6 ++--- 7 files changed, 90 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 .golangci.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..0ae9ec6 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +name: lint + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + golangci-lint: + runs-on: ubuntu-latest + steps: + - name: Check out source + uses: actions/checkout@v6 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v7 + with: + version: v2.5.0 + env: + CGO_ENABLED: "1" diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e304989 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,34 @@ +version: "2" + +run: + build-tags: + - sqlite_fts5 + +linters: + # "standard" enables: errcheck, govet, ineffassign, staticcheck, unused. + default: standard + enable: + - gocritic + - misspell + - unconvert + + settings: + errcheck: + exclude-functions: + # It is safe to ignore errors from Close in deferred calls; + # the primary operation's error is already handled. + - (*database/sql.Rows).Close + - (*database/sql.Stmt).Close + - (*database/sql.DB).Close + - (*os.File).Close + - (io.Closer).Close + - (*github.com/tracecc/tracecc/internal/bundle/sqlite.Bundle).Close + +formatters: + enable: + - goimports + +issues: + # Show all issues, don't cap per-linter. + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/AGENTS.md b/AGENTS.md index 49e8641..136b26c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,12 @@ CGO_ENABLED=1 go test -tags sqlite_fts5 ./pkg/tracecc -run TestCompileRenderSear CGO_ENABLED=1 go test -tags sqlite_fts5 ./internal/adapter/claude/ ``` -There is no linter or Makefile configured. A release CI workflow (`.github/workflows/release.yml`) runs tests and builds on tag push, but there is no PR or push CI. +```bash +# Lint (requires golangci-lint v2) +CGO_ENABLED=1 golangci-lint run ./... +``` + +A release CI workflow (`.github/workflows/release.yml`) runs tests and builds on tag push. A lint CI workflow (`.github/workflows/lint.yml`) runs golangci-lint on pushes to main and on pull requests. ## Architecture diff --git a/internal/adapter/codex/adapter.go b/internal/adapter/codex/adapter.go index 0d13a82..1832e77 100644 --- a/internal/adapter/codex/adapter.go +++ b/internal/adapter/codex/adapter.go @@ -437,10 +437,6 @@ func valueSlice(value any) []map[string]any { return adapter.ValueSlice(value) } -func valueString(value any) string { - return adapter.ValueString(value) -} - func intValue(value any) int { switch typed := value.(type) { case float64: diff --git a/internal/adapter/hermes/adapter_test.go b/internal/adapter/hermes/adapter_test.go index 93ff91b..2560065 100644 --- a/internal/adapter/hermes/adapter_test.go +++ b/internal/adapter/hermes/adapter_test.go @@ -57,20 +57,20 @@ func TestParseCurrentHermesFixture(t *testing.T) { } wantRoles := []canon.RoleKind{ - canon.RoleMetaHeader, // session_meta - canon.RoleUser, // user message - canon.RoleThinking, // assistant reasoning - canon.RoleAssistant, // assistant text - canon.RoleUser, // user message - canon.RoleThinking, // assistant reasoning (tool_calls) - canon.RoleToolCall, // search_files - canon.RoleToolResult, // search result - canon.RoleThinking, // assistant reasoning - canon.RoleAssistant, // "Found it. Let me read it." - canon.RoleToolCall, // read_file - canon.RoleToolError, // permission denied - canon.RoleThinking, // final assistant reasoning - canon.RoleAssistant, // final assistant text + canon.RoleMetaHeader, // session_meta + canon.RoleUser, // user message + canon.RoleThinking, // assistant reasoning + canon.RoleAssistant, // assistant text + canon.RoleUser, // user message + canon.RoleThinking, // assistant reasoning (tool_calls) + canon.RoleToolCall, // search_files + canon.RoleToolResult, // search result + canon.RoleThinking, // assistant reasoning + canon.RoleAssistant, // "Found it. Let me read it." + canon.RoleToolCall, // read_file + canon.RoleToolError, // permission denied + canon.RoleThinking, // final assistant reasoning + canon.RoleAssistant, // final assistant text } for i, want := range wantRoles { if events[i].Role != want { diff --git a/internal/bundle/sqlite/bundle.go b/internal/bundle/sqlite/bundle.go index 9450c68..adb87d2 100644 --- a/internal/bundle/sqlite/bundle.go +++ b/internal/bundle/sqlite/bundle.go @@ -992,8 +992,8 @@ func (b *Bundle) loadSessionView(ctx context.Context, sessionID string) (render. node.ContentLines = canon.SplitLines(content) if metadataJSON != "" { if err := json.Unmarshal([]byte(metadataJSON), &node.Metadata); err != nil { - return render.SessionView{}, fmt.Errorf("unmarshaling node metadata: %w", err) - } + return render.SessionView{}, fmt.Errorf("unmarshaling node metadata: %w", err) + } } view.Nodes = append(view.Nodes, node) } @@ -1036,8 +1036,8 @@ func (b *Bundle) loadAllSessionViews(ctx context.Context) (render.SessionView, e node.ContentLines = canon.SplitLines(content) if metadataJSON != "" { if err := json.Unmarshal([]byte(metadataJSON), &node.Metadata); err != nil { - return render.SessionView{}, fmt.Errorf("unmarshaling node metadata: %w", err) - } + return render.SessionView{}, fmt.Errorf("unmarshaling node metadata: %w", err) + } } view.Nodes = append(view.Nodes, node) } diff --git a/pkg/tracecc/tracecc.go b/pkg/tracecc/tracecc.go index 19b3469..c425df2 100644 --- a/pkg/tracecc/tracecc.go +++ b/pkg/tracecc/tracecc.go @@ -471,9 +471,9 @@ func (a *App) Lines(ctx context.Context, bundlePath string, sessionID string, st // agentTraceRoot defines a non-project-scoped agent whose trace root can be // registered independently via setup --. type agentTraceRoot struct { - Name string // adapter name, also the CLI flag - SubDir string // path under $HOME (e.g. ".hermes/sessions") - DotDir string // the dot-directory to check (e.g. ".hermes") + Name string // adapter name, also the CLI flag + SubDir string // path under $HOME (e.g. ".hermes/sessions") + DotDir string // the dot-directory to check (e.g. ".hermes") } var knownAgentRoots = []agentTraceRoot{ From 003112d3b94feb08f712ec4b1330e86a1fa32c0b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Apr 2026 02:05:58 +0000 Subject: [PATCH 2/2] Update CONTRIBUTING.md to reference golangci-lint Replace the "no linter is configured" note with instructions to run golangci-lint before submitting. https://claude.ai/code/session_01WQDT6GkU9KZX57VdE5jZBi --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a597dbd..c9910a2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,7 +50,7 @@ No renderer changes should be needed. Adapters emit `canon.CanonicalEvent` struc ## Style -- No linter is configured. Keep code consistent with what's already there. +- Run `CGO_ENABLED=1 golangci-lint run ./...` before submitting. CI enforces this on all PRs. - Don't add comments that restate the code. Add comments that explain why. - Error messages should be lowercase and actionable.