Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
356 changes: 356 additions & 0 deletions .agents/skills/ub-go/SKILL.md

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions .agents/skills/ub-go/assets/golangci-template/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# UB Go starter profile for golangci-lint.
# Starter only: adapt enabled linters, exclusions, and settings to repo truth.
# Requires a golangci-lint major version that uses this config schema
# (declared by the `version` key below); see
# references/golangci-config-resolution.md in the ub-go skill.
version: "2"

linters:
# `standard` enables the curated default set (errcheck, govet, ineffassign,
# staticcheck, unused). Additions below are high-signal, low-noise picks.
default: standard
enable:
- misspell
- nolintlint
- revive
- unconvert
- unparam
settings:
nolintlint:
# Inline suppressions must say which linter and why.
require-explanation: true
require-specific: true
exclusions:
# Keep lint noise out of generated code; extend paths to repo truth.
generated: lax
paths:
- third_party$
- builtin$
- examples$

formatters:
enable:
- gofmt
- goimports
260 changes: 260 additions & 0 deletions .agents/skills/ub-go/references/go-standards.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# Go Standards (Portable, Non-Formatting, Latest Stable)

> Verify these standards against the latest official Go documentation and
> release notes via web search.

This reference codifies Go standards for the target repository or project
after local module and tooling truth has been inspected.
It is aligned to official Go and tooling guidance without assuming one
specific host repository.

## 1. Platform and Environment

- Runtime baseline: latest stable Go (detect from `go.mod` and verify via web
search).
- Module mode is the baseline; treat `go.mod` as the source of truth for the
module path, language version, and dependency floor.
- Use `go.work` for multi-module local development instead of committing
`replace` directives meant only for local work.
- Pin developer tools with `tool` directives in `go.mod` and run them via
`go tool` when the module floor supports it; treat the older `tools.go`
blank-import pattern as migration-only.
- Keep builds reproducible: pinned tool versions, tidy module files, and no
machine-specific paths.

## 2. Implementation Strategy

- Prefer clear, boring Go over clever abstractions; readability is a hard
constraint.
- Keep packages small and purpose-named; avoid `util` and `common` grab bags.
- Implement against the module's declared Go version, but bias design toward
the next stable upgrade path instead of preserving older-version behavior by
default.
- Use streaming (`io.Reader`/`io.Writer`) over whole-buffer processing for
large data.
- For non-trivial work, explicitly compare at least two approaches with
pros/cons.

## 2A. Source Truth And Disclosure

- Treat target-project truth as the shipping constraint: detect the active Go
version, build tags, and tooling before recommending implementation details.
- Treat official Go docs and release notes as the preferred guidance baseline
for forward-looking design and migration-ready patterns.
- If official docs, target-project truth, or live code reality materially
disagree on a non-trivial recommendation, surface `OFFICIAL_CONFLICT`,
implement the project-safe path, and explain the forward migration path.
- If a non-trivial claim cannot be confirmed in official sources after
targeted research, mark it `UNVERIFIED` or avoid presenting it as settled
guidance.
- Keep these disclosures scoped to non-trivial, version-sensitive, or
contested guidance rather than every trivial edit.

## 3. Interfaces, Generics, and API Design

- Define interfaces at the consumer, keep them small, and let concrete types
satisfy them implicitly.
- Accept interfaces, return concrete types in exported APIs when practical.
- Use `any` in new code instead of `interface{}`.
- Use generics for real duplication in containers, algorithms, and typed
helpers; avoid speculative type parameters on APIs with one concrete use.
- Newer floors extend generics with generic type aliases and generic
methods; use them where they simplify real APIs, and remember interfaces
still cannot declare generic methods.
- Prefer standard library generic helpers (`slices`, `maps`, `cmp`) when the
module floor provides them, instead of experimental package variants or
hand-rolled loops.
- Design useful zero values; when a zero value cannot be valid, provide a
constructor and document it.
- Keep exported surfaces minimal; start unexported and promote deliberately.

## 4. Error Handling

- Wrap with `fmt.Errorf("context: %w", err)`; inspect with `errors.Is` and
`errors.As`; aggregate with `errors.Join` when several failures matter.
- Choose the error contract deliberately:
- sentinel errors for simple, stable conditions callers match on
- typed errors for structured data callers need
- opaque wrapped errors when callers only need the message chain
- Do not panic for expected failures; reserve `panic` for programmer errors
and unrecoverable invariants, and never let it cross package boundaries in
libraries.
- Return early on failure and keep the happy path at minimal indentation.
- Do not discard meaningful errors with `_`; if an error is intentionally
ignored, make that visible and justified.

## 5. Concurrency

- `context.Context` is the first parameter of request-scoped and long-running
functions; propagate cancellation and deadlines instead of inventing custom
stop channels.
- Prefer structured concurrency (`errgroup` or equivalent) over hand-managed
`sync.WaitGroup` plus error plumbing.
- Every goroutine needs an owner and an exit condition; unbounded goroutine
spawning and fire-and-forget goroutines are defects.
- Channels transfer ownership and signal; mutexes guard simple shared state.
Do not mix both around the same state.
- Use the race detector (`go test -race`) whenever concurrency is touched.
- For CPU-bound parallelism, size worker pools from workload and available
cores instead of unbounded fan-out.

## 6. Modern Standard Library Preferences

- `log/slog` for structured logging with levels and key-value context.
- `slices`, `maps`, and `cmp` for comparisons, sorting, and collection
helpers.
- Builtin `min`, `max`, and `clear` where they improve clarity.
- Current standard library random APIs instead of deprecated global seeding
patterns.
- Range-over-integer and iterator-based range loops (`iter.Seq`) where the
module floor supports them and they improve clarity.
- `errors.Join`, `context.WithTimeoutCause`, and cause-aware cancellation
where failure attribution matters.
- `os.Root` for filesystem access rooted in untrusted or user-supplied
directories, so path traversal and symlink escapes fail by construction.
- `unique.Handle` for interning comparable values in canonicalization-heavy
or cache-heavy code paths.
- The current `encoding/json` implementation and its options; check the
module floor before relying on v2-specific behavior such as strict UTF-8
rejection.

Notes:

- Loop variables are scoped per iteration in current Go; do not add copy
workarounds (`v := v`) in code targeting a floor that already has this
behavior.
- Check the module's `go` directive before using newer standard library
packages; recommend the upgrade rather than silently emitting incompatible
code.

## 7. Imports and Dependency Hygiene

- Group imports as stdlib, third-party, local; keep grouping consistent with
the repo formatter (`gofmt`/`goimports`/`gofumpt`).
- Prefer the standard library first; add dependencies only when they provide
clear correctness, maintainability, or DX value.
- Keep `go mod tidy` clean and review indirect dependency growth.
- No dot imports outside narrowly scoped test helpers.
- Pin tool dependencies with `go.mod` `tool` directives (or the legacy
`tools.go` pattern on older floors) so generated code is reproducible.

## 8. Toolchain Policy

### Baseline

- `gofmt` formatting is non-negotiable; `gofumpt` is an optional stricter
layer a repo may adopt.
- `go vet` runs on touched packages as a minimum static check.
- golangci-lint aggregates linters when the repo adopts it; treat its active
config as the lint policy source of truth.
- `govulncheck` scans for known vulnerabilities reachable from the code path;
recommend it for services and released tooling.

### Tooling Stance (Primary + Fallback)

Primary stack:

- Build/test: `go build`, `go test` (with `-race` where concurrency matters)
- Vet: `go vet`
- Lint aggregation: `golangci-lint`
- Vulnerability scanning: `govulncheck`

Fallback options when constraints require them:

- `staticcheck` standalone when golangci-lint is not adopted
- plain `go test ./...` workflows when no task runner exists

Operational guidance:

- Treat actual lint config files as the policy source of truth when they
exist; use starter scaffolds only when a repo wants this house style and
does not already have active config.
- After generating or modifying Go code, run the configured checks that apply
to the touched scope instead of assuming code generation is sufficient.
- Start with touched packages or targeted tests, then widen only when
failures indicate broader impact.
- If configured tooling cannot be run, report the exact blocker.
- If linting, vulnerability scanning, or testing is missing or not wired into
routine workflows, warn the user and recommend concrete additions rather
than silently accepting the gap.

## 9. Function and API Design

- Keep functions focused, composable, and testable.
- Use narrow signatures; avoid boolean parameter flags when separate functions
or an options struct is clearer.
- Use functional options or config structs for exported constructors with many
optional settings; pick one style per package and stay consistent.
- Validate boundary data near entry points (HTTP handlers, CLI parsing, queue
consumers), not deep in domain logic.
- Document exported identifiers with standard doc comments that start with the
identifier name.

## 10. Architecture and Boundaries

- Preserve layered flow: transport -> service -> repository -> data.
- Keep domain language consistent between implementation and tests.
- Avoid leaking storage or transport details into domain logic.
- Prefer composition over embedding-driven pseudo-inheritance when both are
viable.
- Keep `internal/` packages for code that must not become public API.

## 11. Testing Practice

- Table-driven tests with subtests (`t.Run`) are the default shape.
- Use `t.Parallel()` for independent tests and `t.Setenv`, `t.TempDir`,
`t.Cleanup` instead of manual setup/teardown.
- Keep unit tests hermetic; put external systems behind interfaces and use
small hand-written fakes before reaching for mock frameworks.
- Add fuzz targets for parser-like and untrusted-input functions when the
repository embraces fuzzing.
- Use `testing/synctest` for deterministic tests of concurrent,
time-dependent code where the module floor provides it, instead of sleeps
and flaky timing assumptions.
- Use benchmarks for performance-sensitive paths and keep them runnable in CI
or on demand.
- Treat missing tests or missing race-enabled runs on concurrent code as
explicit quality risks worth reporting.

## 12. Legacy Patterns to Avoid (Migration-Only Exceptions)

- `interface{}` in new code where `any` is available.
- Deprecated `io/ioutil` functions in new code.
- Deprecated global random seeding patterns in new code.
- Loop-variable copy workarounds on floors with per-iteration scoping.
- Experimental package imports (`golang.org/x/exp/...`) for APIs the standard
library already provides at the module floor.
- `GOPATH`-dependent layouts or tooling assumptions.
- Panic-driven control flow for expected errors.
- Dot imports outside narrowly scoped test helpers.

Exception policy:

- Temporary retention is allowed only for explicit compatibility constraints.
- Document what is retained, why it remains, and the follow-up modernization
step.

## 13. Source Map (Official References)

- Go release notes index: <https://go.dev/doc/devel/release>
- Go modules reference (`tool` directives): <https://go.dev/ref/mod>
- Workspaces (`go.work`): <https://go.dev/doc/tutorial/workspaces>
- Traversal-resistant file APIs (`os.Root`): <https://go.dev/blog/osroot>
- Deterministic concurrency tests: <https://go.dev/blog/synctest>
- Value interning (`unique`): <https://go.dev/blog/unique>
- Effective Go: <https://go.dev/doc/effective_go>
- Go Code Review Comments: <https://go.dev/wiki/CodeReviewComments>
- Generics tutorial: <https://go.dev/doc/tutorial/generics>
- `log/slog` docs: <https://pkg.go.dev/log/slog>
- `slices` docs: <https://pkg.go.dev/slices>
- `maps` docs: <https://pkg.go.dev/maps>
- `cmp` docs: <https://pkg.go.dev/cmp>
- `errors` docs (`Is`, `As`, `Join`): <https://pkg.go.dev/errors>
- `context` docs: <https://pkg.go.dev/context>
- `errgroup` docs: <https://pkg.go.dev/golang.org/x/sync/errgroup>
- Data race detector: <https://go.dev/doc/articles/race_detector>
- Fuzzing: <https://go.dev/doc/security/fuzz/>
- Vulnerability management (`govulncheck`): <https://go.dev/doc/security/vuln/>
- golangci-lint docs: <https://golangci-lint.run/>
- Staticcheck docs: <https://staticcheck.dev/docs/>
- gofumpt: <https://github.com/mvdan/gofumpt>
54 changes: 54 additions & 0 deletions .agents/skills/ub-go/references/golangci-config-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# golangci-lint Config Resolution

Use this reference when deciding what lint policy actually applies in a target
repository and whether the bundled starter should be scaffolded.

## Resolution Order

1. An explicit config path passed on the command line or in CI flags wins.
2. Otherwise golangci-lint searches from the working directory upward for
`.golangci.yml`, `.golangci.yaml`, `.golangci.toml`, or `.golangci.json`.
3. If no config exists, golangci-lint runs with its built-in defaults.

Treat the first config found through that order as the repository's real lint
policy. Do not present the bundled starter as active policy when a local
config already exists.

## Config Format Awareness

- Current golangci-lint major versions declare a `version` key at the top of
the config file and split linters and formatters into separate sections.
- Older configs without a `version` key may target a previous major version;
check the installed golangci-lint version in CI or tool manifests before
editing rules, and prefer the project's official migration command over
hand-porting settings.
- Do not mix config schema generations in one file.

## What Counts As Real Policy

1. the discovered config file and its enabled/disabled linters
2. CI flags that override or scope lint runs
3. inline `//nolint:` directives with reasons, as bounded local exceptions

## Scaffolding Behavior

Use `scripts/scaffold_golangci.py` with `assets/golangci-template/` only when:

1. no golangci-lint config exists anywhere in the repository, and
2. the adopting repository wants this catalog's starter baseline

The scaffold copies files only when they are missing and reports write/skip
results deterministically. It never overwrites existing config, installs
binaries, or mutates CI.

## Adaptation Points After Scaffolding

1. align the lint run with the module's Go version (golangci-lint reads it
from `go.mod`; pin explicitly only when needed)
2. review the enabled linter set against repo noise tolerance
3. add exclusion paths for generated code, vendored code, or migration
directories
4. wire the lint command into the repo's task runner or CI so the policy is
actually enforced
5. declare how golangci-lint is installed (CI action, tool dependency, or
documented local install) instead of assuming a global binary
Loading