go-code
Skill7 starsChanged 9 days agoApache-2.0View on GitHub
Use when writing or fixing Go code (a behavior-preserving refactor routes to go-code-refactor), whether or not the task has one obvious topic — it loads the go-* skills the task needs and runs the closing gate. Also use when asked to implement a Go package, function, or handler whose declarations and documentation already exist — write the bodies, fill in a stub, replace panic("not implemented") — even if the request names only the package. Use it too when /go-code modifies another workflow (e.g. /opsx:apply /go-code); as a modifier it selects Go rules, not a change name or path.
In this repository25 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- go-code-refactorSkill
- go-code-reviewSkill
- go-concurrencySkill
- go-contextSkill
- go-databaseSkill
- go-data-structuresSkill
- go-defensiveSkill
- go-documentationSkill
- go-error-handlingSkill
- go-functionsSkill
- go-genericsSkill
- go-httpSkill
- go-interfacesSkill
- go-lintingSkill
- go-loggingSkill
- go-namingSkill
- go-packagesSkill
- go-performanceSkill
- go-resilienceSkill
- go-securitySkill
- go-style-coreSkill
- go-testingSkill
- go-troubleshootingSkill
The file
We split this skill into the rules it gives an agent
9 rules. About 0 are common, 5 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈5
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 53
Implement the authorized scope: Every task — new code, a fix, a restructuring — climbs the restraint ladder in LADDER.md before each …
- Very specific: line 136
Run routine checks inline.
- Distinctive: line 142
Start at the required entry point and its callers.
- Very specific: line 150
Before the first production edit, turn the documentation and the request into a table-driven test in the package — a new …
- Very specific: line 213
Not its Go version: the current form at the module's go directive is written even beside an older neighbor (Write Current Go).
- Distinctive: line 222
When the Contract Table passes, walk the diff once, top to bottom, and delete what the test already proves or the code already shows.
- Distinctive: line 256
Read the changed path from its entry point to its result or side effect.
- Very specific: line 353
Leave complexity headroom: Before a function grows or two merge, read the service's complexity linters in its golangci config (gocyclo, …
- Distinctive: line 387
Every owner in Route Before The First Edit.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Go Code Profile
Route Go work to the relevant owners, then close with their verification gate.
Compatibility: Baseline Go 1.27 (see COMPATIBILITY.md).
Resource Routing
Sibling skills resolve relative to this installed directory; scripts run from the target project. A skill is loaded when
its SKILL.md is in context: the Skill tool where the host has one, else a read of ../<name>/SKILL.md. Report missing resources.
../go-style-core/SKILL.md— Load on every task (step 2); it carries the idiom card, and its references are read only for a decision the task requires.../go-linting/SKILL.md— With a shell, load alongside go-style-core at step 2; its gate and receipt verifier close step 6.references/INTENSITY.md— Read when the prompt carriesliteorultra(/go-code ultra <task>,ultra mode): what each level changes.references/NEW-CODE-EXAMPLES.md— Read when the shape of a Contract Table case, Plain Code body, or budgeted helper is in doubt.../go-code-refactor/references/LADDER.md— Read before the first edit on every task, unless## The Restraint Ladderis already in context (the plugin's session-start hook prints the whole file): step 5 climbs it for each added entity.../go-code-refactor/references/OVER-ENGINEERING.md— Read its replacement table only when seeking a simpler existing API.
Workflow
- Resolve invocation.
$go-code <task>or/go-code <task>selects Go work; a leadinglite,full, orultrasets the restraint level (INTENSITY.md), not the task. An invocation inserts this file only; steps 2 and 3 load the rest. As a modifier (/opsx:apply add-auth /go-code) it is never a change name or path, and the host keeps workflow state, checkpoints, and delegation policy. - Load
go-style-coreand read the code. With a shell, loadgo-lintingin the same initial Skill message.go-style-corecarries the idiom card. Then inspect repository instructions,go.mod(itsgodirective sets the idiom), neighboring code and tests. Without a shell tool, step 6 runs nothing andgo-lintingstays unread. A new function, package, or stub body makes step 4 apply. A fix skips the table but adds one case that fails on the code before it and passes after (go-troubleshooting), sogo-testingis loaded for it too; a restructuring skips both. - Load the owners before the first edit. Match the task against
Route Before The First Edit and load each
matched owner plus every
Also loadentry whose condition holds, all in one message. When step 4 applies or the task is a fix,go-testingis one of them. Select by the decisions being changed: a routine local variable oriftriggers no owner. No edit before every selected skill is in context; add owners when new evidence requires them. Wait for successful Skill results before the edit message; do not dispatch loads and edits together. - Write the Contract Table, for new code only. Turn the documentation and the request into the test file the Contract Table describes before the first production edit: a case written after the body checks only what the body already does. Its cases are what step 6 runs or reads.
- Implement the authorized scope. Every task — new code, a fix, a
restructuring — climbs the restraint ladder in
LADDER.md
before each helper, type, layer, interface, option, or import it adds,
once per entity, and a fix lands at the root cause the ladder names. New
code takes the Plain Code form, counts every package-level
declaration that survives the ladder in the
Declaration Budget, and gets the
Delete Pass and Reader Pass once its cases
pass. Restructuring follows the
delete-first priority. Preserve
required behavior, validation, security controls, and meaningful tests. A
task that does not ask for a dependency adds none:
go.modstays as it is, and a test compares with the standard library (go-testing owns the assertion rule). Resolve routine choices without stopping; ask only for missing information that changes correctness, scope, or authorization. Verify and report. With a shell, load the
go-lintingskill (Skilltool with the router's namespace, otherwise read itsSKILL.md), select the gate scope, then run the Contract Table file with that gate. Hook receipts count only as go-linting verifies them. Without a shell, read each case against its code path. Report as go-style-core says, in this shape and order:<what the change does, in one sentence> checks: gofmt pass · vet pass · test -race pass · lint pass added package-level declarations: 1 — parseLimit: handleList, handleSearch <one sentence per material gap, or nothing>Name the test file and result, not each case.
Route Before The First Edit
The third column adds owners only under its stated condition. Prefer the
narrower owner when topics overlap; a local or unexported identifier alone
does not require go-naming or go-documentation.
| Task touches | Skill | Also load |
errors, wrapping, errors.Is/errors.AsType | go-error-handling | — |
| goroutines, channels, mutexes, races, timers and tickers | go-concurrency | go-context if anything is cancelled |
context.Context, timeouts, cancellation | go-context | go-concurrency if goroutines are started |
tests, table-driven cases, synctest | go-testing | — |
| API naming changes or a naming question | go-naming | — |
| new or changed exported API, doc comments | go-documentation | go-naming if API names change; go-functions if an existing exported signature, interface, or struct changes |
| interfaces, embedding, test doubles, pointer vs value receivers | go-interfaces | — |
function API design, constructor configuration, ordering, signatures, Printf helpers | go-functions | — |
| slices, maps, arrays, sets | go-data-structures | — |
| declaration, enum, or initialization decisions | go-style-core references | — |
| loop/switch mechanics or statement scoping decisions | go-style-core references | — |
| type parameters, constraints, generic methods | go-generics | — |
slog, log levels, request-scoped fields, metrics and trace correlation | go-logging | go-security if a secret or PII could reach a log line |
panic/recover and Must helpers, a deferred closure or a written file's Close, boundary copies, mutable globals, nil/aliasing/overflow traps, time.Time/Duration values | go-defensive | — |
| a cache, hot paths, allocations, benchmarks | go-performance | go-troubleshooting if the cause of slowness is unknown |
| package layout, imports, dependencies | go-packages | — |
| restructuring or deleting existing code | go-code-refactor | — |
| linter config, CI checks | go-linting | — |
| HTTP handlers, routing, middleware, servers, clients; gRPC services and clients | go-http (GRPC.md for gRPC) | go-security if input reaches a file, shell, URL, or template |
| SQL queries, transactions, repositories, migrations | go-database | go-security if a request value picks a row by ID or names a column, table, or sort order |
| untrusted input, secrets, tokens, TLS, cookies | go-security | go-defensive |
| retries, idempotency, circuit breakers, overload, backpressure, fallback | go-resilience | the HTTP, SQL, context, or concurrency owner when its mechanics change |
| ticket, wrong result, environment regression, panic, hang, leak, flaky test; cause unknown | go-troubleshooting | the owner of the mechanism once found |
| JSON and other wire formats, struct tags | go-http (JSON-V2.md) | go-defensive (tags) |
CLI entry point, flags, main/run | go-packages | — |
| a list of findings from a review or audit | the rows the findings name | per area, not per task |
Close With The Gate
Select checks with go-linting for the requested scope; a repository gate replaces the defaults rather than joining them. Bundled scripts add evidence the gate lacks:
- Error handling:
bash <installed-skill-dir>/../go-error-handling/scripts/check-errors.sh ./.... - Exported API documentation:
bash <installed-skill-dir>/../go-documentation/scripts/check-docs.sh. - Before submitting: go-code-review.
<installed-skill-dir> is the directory the host loaded this file from; each
command runs from the project with that path written out, through bash: an
install can drop the scripts' executable bit.
Run routine checks inline. The report carries only results observed for the
current diff and scope, each as pass, fail, unavailable (reason), or
skipped (reason) per go-linting.
Writing New Code
Start at the required entry point and its callers. Implement the documented
success and failure paths within the existing API; design a new API only when
the task calls for one. A stub's panic("not implemented") is missing
behavior, not a refactor contract: replace it and satisfy the acceptance
tests.
Contract Table
Before the first production edit, turn the documentation and the request into
a table-driven test in the package — a new <pkg>_contract_test.go, or cases
added to the existing test file — one case per observable clause of the
documentation, not per branch of the body
(go-testing says which cases earn
a place): each request or input class, each method and status code,
ordering, the error a caller matches and any error text the documentation
promises, and the empty, nil, and invalid inputs; a clause that
says what the code must not do is a case too. The empty case is built from
nil — a nil slice, a nil map, a request with no body — not from an empty
literal: []T{} already has the shape the case is meant to prove, and
slices.Clone of nil is nil. A case for a list that may come back empty
compares the body text with [] (strings.TrimSpace(rec.Body.String())),
never a decoded value:
json.Unmarshal decodes null to a nil slice and [] to an empty one, both
of length zero, and the case passes on the one body the contract forbids. A
clause written as a class — "any other method", "any other value" — takes its
case from the member a library default treats unlike the rest: for "any method
other than GET" that is one HEAD request per GET path, the health check
included, since a GET pattern answers HEAD with 200 on its own.
Where a case contradicts a standard-library default — a nil slice encoding as
null, a GET pattern answering HEAD with 200 — the contract wins, and the
default is overridden in code rather than explained in prose. A failing or
unrunnable case is reported as such, never deleted or weakened to pass; an
expectation that turns out wrong is corrected against the documentation, not
against the implementation. The file is plain code too: one case struct, one
table, one t.Run loop, and a failure message in the Func(input) = got, want
form; go-testing owns the table-test form, and
NEW-CODE-EXAMPLES.md shows a worked table.
Plain Code
The body reads as the documentation reads: reject invalid states early (the early return), then do the main work in its natural order. Return directly when that makes the result clear; do not introduce a result variable to enforce one final return. This applies to the entry point, anything it calls, and the contract test.
- Vocabulary from the specification. The doc comment's nouns name the variables.
- Smallest scope that works. A document built in one function is an
anonymous struct or a type declared inside that function; a step done once
is inline unless it sits at another level of abstraction
(Declaration Budget rule 4); an error carrying
context is
fmt.Errorf("op %q: %w", key, err), matched by the caller witherrors.Is. - Names that explain, states that stay. An intermediate value gets a name when its expression nests deeper than one call or the name says what the expression does not; otherwise it is written where it is used. A fact the code tracks — what was already asked for, what was sent, what failed — keeps its own variable even when another one almost holds it.
- Use a standard-library operation when it states the intent more clearly (the idiom card lists them); a loop that tracks several related facts may read better as a loop. Check ordering, ownership, and nil-versus-empty results. Reach For What Go Ships lists replacements; go-data-structures owns collection semantics.
- New JSON is
encoding/json/v2. A package with noencoding/jsonimport writesimport json "encoding/json/v2"(Go 1.27) and nomake([]T, 0, n)for the wire; one on v1 keeps v1. JSON-V2.md owns what each writes for nil. - Neighbors set the register. Where the package has code, match its
naming and comment density (House Style).
Not its Go version: the current form at the module's
godirective is written even beside an older neighbor (Write Current Go).
NEW-CODE-EXAMPLES.md carries a whole documented JSON document in this form.
Delete Pass
When the Contract Table passes, walk the diff once, top to bottom, and delete what the test already proves or the code already shows. Each item below is a line a reviewer sends back:
- A comment that narrates the next line or repeats what the code already shows. A comment giving a reason the code cannot show stays (go-style-core owns comments).
- A name used once that explains nothing, and the variable it fills: the value is written where it is used. A name that passes the Plain Code test stays.
- A field, header, or option set to what the library uses when it is
absent:
MaxHeaderBytes: 1 << 20ishttp.DefaultMaxHeaderBytes,Content-Type: text/plain; charset=utf-8beforew.Write([]byte("ok"))is what the writer sniffs for a body the code fixes — a body that carries input keeps itsContent-Type(go-security) — andfalse,0,nil, or""in a keyed literal is the zero value. The line says nothing its absence does not. - A
fmt.Errorfwrap that only restates the call:return json.Marshal(doc). The failure branch stays, sincejson.Marshalfails on input (invalid UTF-8 or atime.Durationunder v2, NaN under v1) and a handler keeps its 500; a branch on a call documented never to fail —(*bytes.Buffer).Write,(*strings.Builder).WriteString,hash.Hash.Write— goes. A write whose error has nowhere to go is discarded in the open,_, _ = w.Write(body)with its reason, never bare:errcheckin the gate reads a bare call as a finding, and the reason on that line is not a comment to delete. - A doc comment on an unexported helper beyond one line; go-documentation owns the exported ones.
The pass runs behind the test, never ahead of it: a line a case needs stays, and validation at a trust boundary, failure behavior, and security controls are not deleted for a smaller diff. Rerun or reread the cases after it.
Reader Pass
Read the changed path from its entry point to its result or side effect. Can a reader find the main decision, error exits, and state the code tracks without chasing wrappers that only rename an operation? Keep a one-use name when it exposes a meaningful boundary or state; keep a helper when it hides details that obscure the caller's decision. If either adds a jump without answering a reader's question, inline it and run the cases again.
Declaration Budget
The specification already declares what the task needs: a body behind an existing signature adds no package-level declaration by default, and the count of those it does add is what the report carries. Count every function, method, type, interface, and variable added at package level in production code across the whole diff; go-testing owns test helpers. A declaration is written when the code shows the need at the moment it is written:
- Two call sites exist in the diff, and the report names both.
- A caller outside the function names it: it reads a field through
errors.AsType, satisfies an interface a consumer declares (go-interfaces owns the shape), or owns a resource whose lifetime outlives one call. A caller that inspects or matches an error reads no field: it useserrors.Ison the wrapped sentinel, and that needs no type. - It is a distinct algorithm — a parser, a scheduler — whose name at the call site says more than its body would, and the body left behind reads top to bottom without it.
- It is a step at another level of abstraction than the rest of the function — building SQL beside the domain rule — even with one call site; the report names its caller and this rule. A helper whose name only restates two or three lines of its body is not such a step: those lines stay inline. In multi-step business rules the parse, the pure match, and the interval arithmetic are such steps (Business Rules).
A function literal bound to a name that captures nothing from the function
around it counts under the same four rules: the count is a record, not a
score, and hiding a helper inside a function changes the number without
changing the code. A closure is for capturing state, and a handler registered
once is written at its registration; the values routes share are locals of
the constructor that the handler literals capture, not fields of a server
type with one method per route.
A representation is a value, not a reason: a wire document, a formatted error,
and a sorted view take the Plain Code form. A helper that names
a step at its caller's own level of abstraction meets none of the four; write
the step inline.
The named helpers in skill examples (validate, writeJSON, writeError)
show the order of an operation, not a list to reproduce.
The budget counts what survives the restraint ladder in LADDER.md, climbed per declaration before the four rules are checked.
The report carries one line — added package-level declarations: N — and,
for each, its name and the call sites or caller that need it. A declaration
whose line cannot name them is removed before the report is written, not
explained in it.
Business Rules
Code that carries multi-step business rules — intervals, history or snapshot processing, reconciling against a set of properties, branching on mode or role — is written as a chain, new or refactored; plain CRUD and glue code are exempt.
- The entry point is the chain. Each step's result is a named value the
next step takes:
a := step1(),b := step2(a),c := step3(a, b). The whole algorithm and its key branches read in that function or one level below it. A struct holding the call's dependencies (ctx,tx, the input) may carry the steps as methods; a fluent chain that passes intermediate results through struct fields hides the data flow and is not written. - Effects at the top, logic below. Deep functions return data; writes and external calls happen in the entry point or a step of its chain. Lifting an effect keeps the order and count of writes (BEHAVIOR-TRAPS.md).
- Untyped data is parsed once. A map, a JSON document, or a database row
becomes a typed struct in one place, which returns the parse error; the
decisions and comparisons after it are pure functions with no
errorresult. Checking the value types and converting them is that one pass, not a validation loop followed by a secondswitch v.(type)in the matcher: the matcher takes the typed struct, never themap[string]any, and holds no type switch or assertion. - A step earns its function by the Declaration Budget:
a step of the entry point's chain, or a pure step below it, is rule 4's
other level of abstraction even with one call site. Three steps are named
here because a body written behind one signature folds them in: the parse
of untyped data into its typed struct, the pure match or decision on that
struct, and the interval or set arithmetic. Each is a package-level
function, not a closure inside the entry point, which keeps the step names,
the branches, and the writes and stays within about 40 lines. Any other
wrapper around one expression or one error check is inlined. Its name says what the
step means in the domain, not the library call behind it: verb-like for an
action or a predicate (
subtract,matches), noun-like for a value it returns (processingInterval);leftDifferencesnames the mechanism andprocessRemindernames nothing (go-naming owns the conventions). - Leave complexity headroom. Before a function grows or two merge, read the
service's complexity linters in its golangci config (
gocyclo,gocognit,funlen,nestif) and stay clear of their limits, not at them.
A fragment of an entry point in that shape; b holds only the dependencies,
bindCombination writes by snapshot and returns what it left uncovered, and
inside it readSnapshot parses each snapshot once so matches stays pure:
interval, ok, err := b.processingInterval()
if err != nil || !ok {
return false, err
}
snapshots, err := b.loadSnapshots(interval)
if err != nil {
return false, err
}
remainders, err := b.bindCombination(interval, snapshots)
if err != nil {
return false, err
}
if !data.Bind {
return true, nil // unbind leaves values outside the combination alone
}
for _, r := range remainders {
if err := b.clear(r); err != nil {
return false, err
}
}
return true, nilRelated Skills
- Every owner in Route Before The First Edit.
- go-style-core: house style, report length, and delegation.
- go-linting: the closing gate.
- go-code-review: the checklist before submitting.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

