一键导入
golang-user-conventions
Users conventions and patterns for Go CLI/TUI projects. Use when working on a Go project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Users conventions and patterns for Go CLI/TUI projects. Use when working on a Go project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Teach a personal editor agent the user's own editing taste by reading the diff between an AI-drafted document and the user's hand-edit of it, encoding the lessons as procedures in the agent, and verifying with controlled re-runs. Use whenever the user has edited an AI-written document (README, docs, blog post) and wants their editor agent to learn from it, wants to build a personal editor agent from scratch, or says things like "teach my editor", "learn my house style", "train the humaniser on my edits", "make the editor agent edit more like me", or asks to compare what their editor agent would do against their own edit of the same document.
Generate a GitHub-style README.md for the current project by reading the actual codebase. Use when the user asks to "generate a readme", "write a readme", "document this project", "create a readme", or invokes /readme. Also callable from the github-create skill.
User's conventions and patterns for native macOS Swift apps. Use when working on a Swift or macOS app project
Streamlined workflow for implementing features with ait issue tracking. Handles: reading project context, checking outstanding work, planning new features, creating ait issues, and implementing with proper acceptance criteria checking. Use when starting a new session or beginning work on a feature.
One-stop review after feature work, or for a whole codebase. Runs deterministic checks (section ordering, arch conventions) free of charge, then fresh-eyes reviewer agents. Covers team conventions, test quality, complexity, security, and Livewire/Flux patterns - plus runtime checks (a11y, cold UX probe) when UI changed. Also has a quick mid-session sanity-check mode ("this seem ok?").
Turn a settled feature discussion or plan document into consultant-ready ait issues — an initiative/epic vision document plus implementation specs a fresh agent could pick up cold, tomorrow. Use towards the end of a feature conversation ("let's get this into ait"), or when converting a plan file. Also the knowledge base for the plan-to-ait agent. For driving the ait CLI itself, see the ait skill.
| name | golang-user-conventions |
| description | Users conventions and patterns for Go CLI/TUI projects. Use when working on a Go project |
| allowed-tools | Read,Write,Edit,Bash,Glob,Grep |
| version | 0.5.0 |
| author | ohnotnow <https://github.com/ohnotnow> |
| license | MIT |
Standards for building small, focused Go tools — single self-contained binaries that are easy to share and cross-compile. These are opinionated defaults, not laws. Scale up or down based on the project.
This skill is split across files. SKILL.md (this file) covers conventions that apply to any Go CLI. Two satellite files cover situational topics — only read them when relevant:
TUI.md — Bubble Tea, lipgloss, huh, theming, the Width() footgun,
shelling out to $EDITOR.
Read when: go.mod imports github.com/charmbracelet/..., or the user's
request mentions a TUI / Bubble Tea / interactive terminal UI / list / form /
picker, or you're starting a new project and the user has described
something interactive.WEB.md — embedded web UI conventions (serve subcommand, go:embed,
http.Handler patterns, JSON API).
Read when: the project has a serve subcommand or imports net/http for
serving (not just fetching), or the user's request mentions a web
dashboard, browser UI, or serve command.PKGSITE.md — the official pkg.go.dev API (v1beta) for querying
module/package metadata, versions, exported symbols, importers, and
vulnerabilities.
Read when: you need to look up the latest tagged version of a third-party
module, check known vulnerabilities, list a package's exports, find what
imports a package, or search the public Go ecosystem — or the user
mentions pkg.go.dev, pkgsite-cli, or the v1beta API.Don't load these defensively — the savings only work if you actually skip
them when they don't apply. If a session shifts direction (a CLI gains a TUI,
a tool grows a serve command), pull the satellite file in mid-session.
Reusable code lives under templates/ (e.g. templates/theme/nord.go for
Nord-themed lipgloss + huh styles) and is referenced from the satellite that
covers it. There is also an example of a straightforward golang GitHub action to build
and publish a release in templates/release.yml taken from a project called 'agent-issue-tracker'
that creates the ait binary.
Choose the simplest layout that fits the project's complexity:
main.go only)For scripts-with-a-GUI. Under ~400 lines, no real domain logic, mostly glue between config/OS/UI. Example: a music shuffler, a quick file picker.
package main, multiple filesWhen you have distinct concerns (storage, domain types, UI) but they're tightly coupled and nobody will import the code as a library. Split by concern:
myapp/
main.go # Entrypoint, arg parsing, wiring
store.go # Database access, queries, migrations
types.go # Domain types, enums, validation
ui.go # Bubble Tea model, update, view
*_test.go # Tests alongside source
go.mod
This is the default for most projects.
internal/<name>/ packageWhen the tool has 5+ source files, subcommands, a public-facing contract (JSON
API, stable exit codes, documented ID format), or genuinely separable concerns.
The internal/ boundary prevents accidental imports.
myapp/
main.go # Thin: flag extraction, open DB, dispatch
internal/myapp/
app.go # Command router, handlers
store.go # DB access, schema, migrations
migrate.go # Migration definitions
types.go # Domain types, validation
config.go # Config detection, defaults
format.go # Output formatting
main_test.go # Integration tests
go.mod
internal/When the project has genuinely separate domains (not just layers of one domain),
use separate packages under internal/. For example, a tool that fetches RSS
feeds, stores data, and serves a web UI has three distinct domains:
myapp/
main.go # Entrypoint, wiring
internal/
db/db.go # Storage layer
models/models.go # Shared data types
youtube/ # External API integration
youtube.go
rss.go
fetch.go
web/ # Embedded web UI
server.go
static/index.html
go.mod
This is appropriate when each package has its own distinct responsibility and the packages communicate through the shared models. Don't flatten genuinely separate domains into a single package just for consistency.
Heuristic: if you're reaching for more than ~4 source files, or the tool has
subcommands, use internal/. Otherwise flat is fine.
Always use flag.FlagSet from the standard library for command-line flags. One
FlagSet per subcommand. Don't hand-roll argument parsing — it leads to
inconsistencies across projects and breaks down with boolean flags, --flag=value
syntax, or help text.
func runAdd(args []string) error {
fs := flag.NewFlagSet("add", flag.ContinueOnError)
category := fs.String("category", "", "category name")
fs.SetOutput(io.Discard) // Suppress default help output
if err := fs.Parse(args); err != nil {
return err
}
// fs.Args() returns remaining positional arguments
url := fs.Arg(0)
// ...
}
For tools with subcommands, dispatch on os.Args[1] (or the first positional
arg) then pass the remaining args to the subcommand's FlagSet. No need for
cobra or other frameworks — flag.FlagSet per subcommand is sufficient for
focused tools.
When a project needs persistent storage, use SQLite.
modernc.org/sqlite — pure Go, no CGo, cross-compiles cleanly._ "modernc.org/sqlite" with database/sql for the driver.Set these pragmas on every connection immediately after opening:
db.ExecContext(ctx, "PRAGMA foreign_keys = ON")
db.ExecContext(ctx, "PRAGMA busy_timeout = 5000")
db.ExecContext(ctx, "PRAGMA journal_mode = WAL")
For in-memory databases (tests), limit to a single connection to keep the DB alive across queries:
db.SetMaxOpenConns(1)
Always use a schema_version table and numbered, forward-only migrations. Users
may skip releases, so each migration must be independently applicable — just
apply all versions newer than the current one.
schema_version table:
CREATE TABLE IF NOT EXISTS schema_version (
id INTEGER PRIMARY KEY CHECK (id = 1),
version INTEGER NOT NULL DEFAULT 0
);
INSERT OR IGNORE INTO schema_version (id, version) VALUES (1, 0);
Migration structure:
type migration struct {
version int
description string
apply func(ctx context.Context, tx *sql.Tx) error
}
var migrations = []migration{
{1, "baseline schema", func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, `CREATE TABLE ...`)
return err
}},
// Append new migrations here. Never modify existing ones.
}
Applying migrations:
func runMigrations(ctx context.Context, db *sql.DB) error {
var current int
db.QueryRowContext(ctx, "SELECT version FROM schema_version WHERE id = 1").Scan(¤t)
for _, m := range migrations {
if m.version <= current {
continue
}
tx, _ := db.BeginTx(ctx, nil)
if err := m.apply(ctx, tx); err != nil {
tx.Rollback()
return fmt.Errorf("migration %d (%s): %w", m.version, m.description, err)
}
tx.ExecContext(ctx, "UPDATE schema_version SET version = ? WHERE id = 1", m.version)
tx.Commit()
}
return nil
}
Scale the migration complexity to the project:
ALTER TABLE ADD COLUMN is fine.~/.config/<toolname>/ using
os.UserConfigDir().CHECK (id = 1).~/.config/<toolname>/config.yaml.
Always ship a .example file. Use gopkg.in/yaml.v3. Provide sensible
defaults so the tool works without any config file.Use a typed error with code, message, and exit code:
type CLIError struct {
Code string // "validation", "not_found", "usage", "internal"
Message string
ExitCode int // 64=usage, 65=validation, 66=not_found, 1=internal
}
Output errors as JSON: {"error": {"code": "...", "message": "..."}}.
Plain error returns from store/logic layers. In the UI, set m.status to a
short human-readable message. No logging frameworks.
testing only. No testify, no ginkgo.:memory: SQLite with a helper:func newTestStore(t *testing.T) *Store {
t.Helper()
s, err := NewStoreWithPath(":memory:")
if err != nil {
t.Fatalf("create test store: %v", err)
}
t.Cleanup(func() { s.Close() })
return s
}
t.Helper() on all test helpers.t.Cleanup() for teardown instead of manual defer.t.Run("subtest name", ...) to organise related assertions.For tools with multiple subcommands, flags, and user-facing IDs, offer a
completion subcommand that prints a bash or zsh completion script. This is
the standard pattern used by kubectl, gh, docker, etc. — the binary generates
its own completion script.
Only worth adding when the tool has enough commands/flags/IDs that tab completion genuinely saves time. A simple TUI app with no subcommands doesn't need it.
completion as a command that does not need the database (so it
works before any init/setup).eval "$(mytool completion bash)" # in .bashrc
mytool completion zsh > ~/.zsh/completions/_mytool # for zsh
Static completions — command names, flag names, enum values (statuses, types, priorities) are baked into the script at generation time:
func generateBashCompletion() string {
cmdNames := strings.Join(CommandNames(), " ")
// Build per-command flag cases from the registry...
return fmt.Sprintf(`_mytool_completions() {
local commands="%s"
# ...
}
complete -F _mytool_completions mytool
`, cmdNames)
}
Dynamic completions — for user data like issue IDs, the generated script calls the tool at tab-time to fetch live values:
# Inside the generated completion script:
ids=$(mytool list --all 2>/dev/null | grep -o '"id": *"[^"]*"' | sed 's/"id": *"//;s/"//')
COMPREPLY=($(compgen -W "${ids}" -- "${cur}"))
This is what makes mytool show f<tab> expand to the full ID — it runs
mytool list in the background and offers matching IDs.
Zsh gets richer output — use _describe for command descriptions and
_arguments for flag documentation. Bash uses compgen -W for simple word
matching.
Ask whether completion would be useful when the project has:
For simpler tools, skip it entirely.
Single self-contained binary. No CGo.
go build -o <name> .
go test ./...
Cross-compile with standard GOOS/GOARCH:
GOOS=linux GOARCH=amd64 go build -o <name>-linux .
GOOS=darwin GOARCH=arm64 go build -o <name>-macos .
GOOS=windows GOARCH=amd64 go build -o <name>.exe .
This skill does not cover: