Skip to content
Back to skills

Write Go Kit

ASecurity

Write or review shared Go libraries under a-novel-kit, including golib and jwt, or decide whether a capability belongs in golib. Apply stricter dependency/package rules alongside write-go; excludes JS/TS and kit/workflows.

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
developmentgogitapibackendsecuritydocumentation

Works with

  • cli
  • api

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add a-novel-kit/stack --skill write-go-kit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Write Go Kit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Write Go Kit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/a-novel-kit-write-go-kit/badge)](https://www.skillsdirectory.com/skills/a-novel-kit-write-go-kit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: write-go-kit
description: >
  Write or review shared Go libraries under a-novel-kit, including golib and jwt, or decide whether
  a capability belongs in golib. Apply stricter dependency/package rules alongside write-go;
  excludes JS/TS and kit/workflows.
---

# Go — a-novel-kit shared libraries

This skill governs Go in the `a-novel-kit` organization: the shared-library repos checked out
under `kit/` (`golib`, `jwt`, and any future graduate). These are **not** services — there is no
`dao`/`services`/`handlers` layering here. Load `write-go` (the base Go conventions) and
`write-go-tests` (and `document-code` when documenting) **alongside** this skill. For a-novel
backend services use `write-go-service` instead.

**Before touching anything**, read the repo's existing code and its README/CONTRIBUTING — kit
repos are small, cohesive, and consistent; copy the established pattern. When the change is needed
to support a service (or vice versa), load **`manage-versions`** as well: kit changes and the
service changes that consume them must be released in the right order.

---

## Why kit exists, and the standing tension

Shared libraries exist so services can be written **faster, with more focus** — by removing
boilerplate and duplicated glue, not by adding capability. Every line that lands in a kit repo is
maintained _forever_ as a community-facing package. So the bar for adding is high, and the default
for "should this go in kit?" is **no** until proven otherwise.

There are two kinds of kit repo:

- **`golib`** — the _tote_ package: a single module (`github.com/a-novel-kit/golib`) whose
  sub-packages each collect the boilerplate for one concern (`config`, `otel`, `httpf`, `grpcf`,
  `logging`, `postgres`, `smtp`). Glue lives here until — if ever — a chunk grows enough to
  graduate.
- **Graduated packages** — `jwt` is the precedent: when a `golib` chunk gets large, has cohesive
  scope, and has value beyond a-novel's internal needs (no suitable Go JWT library existed, so one
  was built), it moves to its own `a-novel-kit/<name>` repo and takes on the full set of
  community-package obligations below.

---

## golib — keep it as small as possible

**Rule #1: `golib` stays minimal. The ideal end state is that `golib` disappears entirely** and
every service depends only on external libraries. We are not there because duplicated boilerplate
is real, and a convenient place to put it is worth a small dependency. But every addition is a step
away from rule #1, so each one is a deliberate trade.

- **Never imitate the capabilities of an existing dependency.** If a well-maintained library does
  the job — even if it does _more_ than we need, even if its ergonomics aren't perfect — use the
  library, directly, even from a service. Do not wrap it in a `golib` sub-package "for
  convenience": a thin wrapper around a good dependency is a maintenance burden that adds nothing.
- **Don't reimplement the standard library.** `golib` is glue, not a re-export of stdlib.

### The "should this go in golib?" decision

A balance, not a checklist. Add a thing to `golib` only when **all** of these hold; if any is
shaky, the answer is no:

1. It is **pure boilerplate / glue** — no business logic, nothing a-novel-specific in its core; it
   does the same mechanical thing regardless of who calls it. (Examples already in `golib`: the
   `otel.Report*` span helpers, `postgres.GetContext`/`RunInTx`/migration plumbing, the `logging`
   presets, the `httpf` error-mapping + JSON helpers, the `config.LoadEnv` parsers.)
2. **Several services would otherwise copy it verbatim.** One service needing it is not enough — a
   one-off belongs inline in that service (or, reluctantly, its near-empty `internal/lib/`). Two
   services with the same copy-paste is the bar.
3. **No dependency already covers it.** If `samber/lo`, `uptrace/bun`, an `otel` contrib package,
   or anything else in (or addable to) `go.mod` does it, use that instead.
4. **You can't make it disappear instead.** Sometimes the right move is to delete the duplicated
   code from all the services and not have it anywhere — if the "boilerplate" is two lines, inline
   beats a `golib` symbol.

When in genuine doubt: **don't add it.** Adding a symbol is cheap; removing one that other repos
import is a coordinated cross-repo release dance (deprecate → migrate every consumer → remove —
see `manage-versions`). That asymmetry is why the bar is high.

### Removing from golib

Maintenance includes shrinking. When a newer upstream subsumes a `golib` helper, **delete the
helper** — staged: deprecate it (a doc comment + a `// Deprecated:` line pointing at the
replacement), drive the consumers to migrate, then remove it in a later release. (See
`manage-versions` for the backward-compatible-staging rule; it applies to every public symbol you
retire.)

---

## Dependencies in kit repos (stricter than the base rule)

`write-go`'s dependency policy applies, with the bar raised:

- The kit exists to consolidate dependencies so services have fewer to vet, so adding a **new**
  dependency to a kit repo — especially to `golib` — works against the goal. Do it only when the
  alternative is hand-rolling something substantial that the new library does well and broadly.
- A kit dependency must be **organization-backed, never tied to a single personal account** —
  org ownership survives a maintainer losing interest, and a public package can't afford a
  surprise-abandoned dep. Well-maintained, broad coverage, active issue tracker: non-negotiable.
- Adding a dependency anywhere needs explicit developer approval (`write-go`). In a kit repo,
  expect the answer to lean "no" — propose it with the alternatives spelled out.

---

## golib structure

- One module, sub-packages by **concern**. A sub-package is an independent unit — keep
  cross-coupling between `golib` sub-packages to the minimum the concern genuinely requires.
- Within a sub-package, separate the **ready-made values** from the **mechanics**: `presets/`
  holds the pre-assembled configs (e.g. `otel/presets`, `postgres/presets`, `logging/presets`),
  `utils/` (or top-level helper files) holds the functions. Keep the top-level package surface of
  a sub-package small and stable.
- `golib` ships a small amount of generated code (the `grpcf` proto bits) — `buf` is in the
  toolchain (`buf.yaml`, `buf.gen.yaml`); `pnpm generate:go` runs `go generate` and `buf generate`,
  `pnpm lint:proto` runs `buf lint`, `pnpm format:proto` runs `buf format`/`buf dep update`. Treat `.proto` in
  a kit repo with the **`write-proto`** skill (the contract-stability rules there apply doubly to a
  shared library).
- Doc comments on every exported symbol (`document-code`) — `golib` is consumed by every service,
  so its godoc _is_ its documentation.

---

## Graduating a sub-package into its own repo

When a `golib` sub-package grows large, has a cohesive scope, and has value beyond a-novel's
internal needs, it earns its own `a-novel-kit/<name>` repo (the JWT precedent). **This is the
developer's call** — when you see a candidate, flag it; do not move it unprompted.

Once graduated, the package is a **public, community-facing library**, and the rules change:

- **Design the API broadly, not for our needs.** A graduated package covers its _domain_, not just
  the slice a-novel happens to use: `jwt` implements JWA / JWE / JWK / JWS / JWP, not only the two
  token flows the auth service needs. Design the exported surface as if strangers will build on it.
- **Documentation beyond godoc.** `jwt`'s README is its user guide, with badges (Go version, CI
  status, codecov) and a `go get` line, and `docs/migrations/` holds its migration guides.
- **Thorough tests + coverage gates.** A `codecov.yml`, high coverage, and `Example_xxx` /
  `ExampleType_method` functions for the public API — those render on pkg.go.dev _and_ run as
  tests, so they keep the docs honest.
- **The `a-novel-kit/.github` org defaults.** `LICENSE`, `SECURITY.md`, `CODE_OF_CONDUCT.md` (the
  Contributor Covenant, verbatim — don't author one), and a `CONTRIBUTING.md` that links the org
  concepts doc at `a-novel-kit/.github/blob/master/CONTRIBUTING.md` rather than restating it.
- **Semver discipline.** Public API stability is why the package graduated into its own versioned
  repo. Deprecate-then-remove; never break the API without a major bump; and even a major bump
  stages the break (add the new path → migrate consumers → remove the old path in a later
  release). See `manage-versions`.

A change to a graduated package that any a-novel service consumes is a cross-repo change — load
`manage-versions` and order the merges (library PR merges + releases first; the service re-pins to
the released tag before merging).

---

## a-novel-kit repo conventions

Match the repo's existing setup; the standard shape:

- **pnpm scripts** — match the repo's existing set (no Makefiles anywhere):
  - `a-novel test -y` — gotestsum via `gotestsum.mod` (the CLI discovers it).
  - `pnpm lint:go` — `golangci-lint` via `golangci-lint.mod`; `pnpm lint:proto` — `buf lint`
    (when there's proto); `pnpm lint` — the prettier/docs tooling.
  - `pnpm generate:go` — `go generate` (+ `buf generate` if proto).
  - `pnpm format:go` — `go mod tidy` + `golangci-lint --fix`; `pnpm format:proto` —
    `buf format` + `buf dep update`; `pnpm format` — prettier.

  `write-go` already says: run `pnpm format:go` and `pnpm lint:go` after every edit;
  `pnpm generate:go` when generated inputs changed.

- **Tool dependencies are pinned in `*.mod` side files** — `golangci-lint.mod`, `gotestsum.mod`,
  `govulncheck.mod` — kept out of the main `go.mod`, invoked via `go tool -modfile=<name>.mod <tool>`.
  Don't move tool deps into `go.mod`.
- **`renovate.json`** keeps dependencies (and the consumers, in service repos) up to date; a
  `pnpm` workspace + `prettier.config.js` cover the docs/markdown tooling.
- **CI is the reusable workflows from `a-novel-kit/workflows`** (`go-actions/lint-go`,
  `go-actions/test-go`, the publish/release actions, etc.). A tag push triggers
  `publish-actions/auto-release` — i.e. **versions are git tags** (`vX.Y.Z`), and the next tag is
  computed from the conventional-commit history on `master`. See `manage-versions`.

---

## Tests in kit repos

`write-go-tests` covers the common shape (table-driven, `t.Parallel()`, `require`, the `_test`
package, helpers, mockery where interfaces exist). The kit-flavoured additions:

- **Coverage is gated** (codecov) — and for a graduated package, expected to be high. Cover the
  public API exhaustively: every exported function, every documented error path, edge cases and
  boundaries. A public library that 80%-covers its own surface is not done.
- **Write `Example_xxx` / `ExampleType_method` functions for the public API.** They double as the
  reference documentation on pkg.go.dev _and_ run as tests, so they can't silently rot. A
  graduated package should have an example for each major entry point.
- **A single shared-fixture sub-package may use a plain name** (`testutils/` — `jwt` has one). The
  layer-prefixed-name rule in `write-go-tests` (`configtest`, `libtest`, …) keeps _several_ fixture
  packages distinct in one repo; a small library with one is fine.
- **No service-DB harness.** Kit libraries have no `dao` layer, so there is no
  `postgres.RunIsolatedTransactionalTest` to use, and most kit tests are pure unit tests. Where a
  sub-package wraps an external system (`postgres`, `smtp`), its tests may stand a real instance up
  via the repo's `scripts/test.sh` — follow whatever the repo already does.

---

## Common pitfalls

(`write-go` lists the language-level ones. These are the kit-specific ones:)

- **Adding a thin wrapper around a good dependency to `golib`.** Use the dependency directly, even
  from a service.
- **Putting a one-off in `golib` because it "might be reused".** Two real consumers, or it stays
  out.
- **Reimplementing stdlib or an existing dep in `golib`.** Rule #1: `golib` shrinks.
- **Removing a public `golib`/graduated-package symbol in one shot.** Deprecate → migrate consumers
  → remove later, ordered per `manage-versions`.
- **Designing a graduated package's API around a-novel's needs only.** Cover the domain.
- **Authoring a bespoke `CODE_OF_CONDUCT.md` / restating the org contributing guide.** Use the
  `a-novel-kit/.github` defaults; the per-repo `CONTRIBUTING.md` only _extends_ the org-wide one.
- **Moving tool dependencies into `go.mod`.** They live in `golangci-lint.mod` / `gotestsum.mod`.
- **An attacker-controlled length reaching a `crypto/cipher` call that panics on it.**
  `cipher.NewCBCDecrypter`, `CryptBlocks` and an AEAD's `Open` panic on a wrong IV, nonce or block
  length. An integrity check does not protect them when the sender can know the key, as with
  public-key key management. Check every length first and return the library's invalid-token
  error. gRPC does not recover a handler panic, so the process exits.
- **Graduating a sub-package without the developer's say-so.** Flag the candidate; let the
  developer decide.
- **Treating a kit change as self-contained when a service needs it.** It's a cross-repo change —
  `manage-versions`: the library merges and releases first, the service re-pins to the released
  tag before merging.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.