agentsclimarketplace

Go project layout

Skill ctoth/golang-skills-plugin/plugins/golang/skills/go-project-layout

Agent skill plugins for Go code quality and performance work.

Install
npx -y skills add ctoth/golang-skills-plugin --skill go-project-layout

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Guides how a Go module is organized into directories and packages — start flat (a single package at the module root is fine; don't build a deep tree prematurely), put each binary behind cmd/<name>/main.go and keep main thin (parse flags, wire dependencies, delegate to importable packages so the logic is testable), use internal/ for compiler-enforced privacy to keep an API surface small, package by responsibility/capability not by MVC layer (no models//controllers//services/), never create a util/common/helpers/shared grab-bag named for what it holds instead of what it does, don't treat the community golang-standards/project-layout repo as official, and don't reach for pkg/ by reflex. Auto-invokes when creating a new Go project/module structure, adding packages or directories, cmd/ or internal/ dirs, or on "how should I structure this", "where does this code go", or "do I need a pkg/ folder". The official guidance is go.dev/doc/modules/layout, and it is minimal.

SKILL.md

16.9 KB, as published. Nobody here has run it

Go Project Layout

"A basic Go package has all its code in the project's root directory." — Organizing a Go module

"A little copying is better than a little dependency." — Go Proverbs

"If you cannot come up with a package name that's a meaningful prefix for the package's contents, the package abstraction boundary may be wrong." — Go Blog — Package Names

Most Go projects are over-structured before they have earned a single directory. The model, trained on Java and Node repositories, scaffolds pkg/, internal/, api/, models/, and util/ around a program that fits in one file — and then scatters one feature across five of those directories. Go's official layout guidance pushes the other way and is deliberately minimal: a module may be a single flat package at its root, and you add structure only when a real boundary appears. This skill owns the structure — directories, package boundaries, internal/, cmd/, and the grab-bag cure. The package name rules live in go-naming-and-style; the over-structuring instinct is axis 2 of go-idiomatic-discipline.


1. The Rules at a Glance

RuleThe disciplineSource
Start flatOne package at the module root is a complete, valid project"A basic Go package has all its code in the project's root directory" (Layout)
Split only on a real boundaryAdd a package when functionality genuinely separates, not preemptively"Larger packages or commands may benefit from splitting off some functionality into supporting packages" (Layout)
Binaries go in cmd/<name>/One directory per command, each with its own main.go"A common convention is placing all commands in a repository into a cmd directory" (Layout)
Keep main thinmain parses flags and wires deps; logic lives in importable packagesA server keeps "the Go packages implementing the server's logic in the internal directory" (Layout)
internal/ is enforced privacyCode under internal/ is importable only from the tree rooted at its parent"verifies that the package doing the import is within the tree rooted at the parent of the internal directory" (Go 1.4)
Package by responsibilityGroup by what code does, not by architectural layer"take the client's point of view" (Package Names)
No util/common/helpersNever name a package for what it holds instead of what it does"Packages named util, common, or misc provide clients with no sense of what the package contains" (Package Names)
No pkg/ reflex, no "standard layout"pkg/ is optional; golang-standards/project-layout is not officialThe official guidance is [go.dev/doc/modules/layout]; it mentions neither
Don't stutter the import pathchess.Board, not models.Board or chess.ChessBoard"the names for those contents need not repeat the package name" (Package Names)

2. Start Flat — A Single Package Is a Complete Project

The official guidance opens with the smallest possible shape, and it is a finished project, not a stepping stone: "A basic Go package has all its code in the project's root directory. The project consists of a single module, which consists of a single package" (Layout).

project-root/
  go.mod
  modname.go
  modname_test.go

A package may already be several files before it needs to become several packages: "A Go package can be split into multiple files, all residing within the same directory" (Layout). You add a directory only when functionality genuinely separates — "Larger packages or commands may benefit from splitting off some functionality into supporting packages" (Layout) — and the test for "genuinely" is the client's experience: "if the user must import both packages in order to use either in any meaningful way, combining them together is usually the right thing to do" (Google Best Practices). Splitting before that boundary exists is the structural form of axis 2 in go-idiomatic-discipline: scaffolding nobody asked for, which the next reader must navigate.

The opposite extreme is also wrong — one package is not infinitely scalable: "putting your entire project in a single package would likely make that package too large" (Google Best Practices). The rule is grow into structure, not start with it or refuse it.


3. cmd/<name>/ for Binaries, and Keep main Thin

When a project produces one or more executables — especially alongside importable packages — the commands go under cmd/: "A common convention is placing all commands in a repository into a cmd directory; while this isn't strictly necessary in a repository that consists only of commands, it's very useful in a mixed repository that has both commands and importable packages" (Layout).

project-root/
  go.mod
  auth/
    auth.go
  cmd/
    prog1/
      main.go
    prog2/
      main.go

The discipline that matters most here is keeping main thin. package main cannot be imported — "Commands are built into binaries and cannot be imported" ([go help packages]) — so any logic you put in main is logic you cannot unit-test and cannot reuse from a second command. main's job is to parse flags, read configuration, wire dependencies together, and call into an importable package that holds the real work. The official guidance models this: a server keeps "the Go packages implementing the server's logic in the internal directory" and the thin entry point in cmd (Layout).

// cmd/greet/main.go — thin: parse, wire, delegate. No business logic.
package main

import (
	"flag"
	"fmt"

	"example.com/greet/internal/greeting"
)

func main() {
	name := flag.String("name", "", "name to greet")
	flag.Parse()
	fmt.Println(greeting.Message(*name)) // logic lives in greeting, which has tests
}

The payoff is that greeting.Message is an ordinary function with a table test, while main stays a few lines that need no test of their own. (This structure builds, vets, and tests clean — see references/common-mistakes.md mistake 4 for the fat-main version and its cost.)


4. internal/ Is Compiler-Enforced Privacy

internal/ is not a naming convention — it is a rule the go command enforces at build time. "When the go command sees an import of a package with internal in its path, it verifies that the package doing the import is within the tree rooted at the parent of the internal directory. For example, a package .../a/b/c/internal/d/e/f can be imported only by code in the directory tree rooted at .../a/b/c. It cannot be imported by code in .../a/b/g or in any other repository" (Go 1.4).

Use it to keep your public API surface small. Anything you do not want external modules to import — and therefore do not want to be bound by — goes under internal/: "Since other projects cannot import code from our internal directory, we're free to refactor its API and generally move things around without breaking external users" (Layout). The guidance is to default to private: "It's recommended to keep packages in internal as much as possible" (Layout).

example.com/greet/
  go.mod
  cmd/greet/main.go              // can import internal/greeting (rooted at module)
  internal/greeting/greeting.go  // logic; private to this module

The enforcement is real, not advisory. A second module importing example.com/greet/internal/greeting fails to compile:

main.go:3:8: use of internal package example.com/greet/internal/greeting not allowed

That compile error is the feature: it lets you publish a module while keeping most of its code refactorable. The go.mod that defines the module boundary (and thus the internal/ root) is owned by go-modules-and-versioning.


5. Package by Responsibility, Not by Layer

A Go package is a capability, named for what it provides, because the package name is a prefix on every identifier the caller writes: "A package name and its contents' names are coupled, since client code uses them together. When designing a package, take the client's point of view" (Package Names). The litmus test is whether a meaningful prefix exists: "If you cannot come up with a package name that's a meaningful prefix for the package's contents, the package abstraction boundary may be wrong" (Package Names).

This rules out the MVC layer splitmodels/, controllers/, services/, repositories/ — that the model ports from Rails or Spring. Layer-based packages scatter one feature across every directory: adding a "chess move" feature touches models/move.go, services/move.go, and controllers/move.go, and the packages name an architectural role, not a capability. Package instead by domain: a chess package owns chess.Board, chess.Move, and chess.Game together, because a client reaching for one reaches for all of them.

// WRONG — by layer; one feature smeared across four packages, names mean nothing useful
models/        controllers/   services/      repositories/
  board.go       game.go        rules.go       store.go

// RIGHT — by responsibility; each package is a capability with a meaningful prefix
chess/         // chess.Board, chess.Move, chess.Game
  board.go  move.go  game.go
storage/       // storage.Save, storage.Load
  store.go

This boundary often follows the consumer-side interface: the package that uses a behavior declares the small interface it needs, which keeps the dependency arrow pointing the right way and avoids import cycles. Interface placement is owned by go-interfaces.


6. The util/common/helpers Grab-Bag Antipattern

The single most common layout failure is a package named for what it contains rather than what it does: util, common, helpers, base, shared, misc. Such a name is an open invitation to accrete unrelated junk, and it tells a caller nothing. "Packages named util, common, or misc provide clients with no sense of what the package contains. This makes it harder for clients to use the package and makes it harder for maintainers to keep the package focused" (Package Names). The same warning applies to dumping every type into one api/types/interfaces package: such packages suffer "growing without bound, providing no guidance to users, accumulating dependencies, and colliding with other imports" (Package Names).

The cure is structural, which is why it lives here and not only in the naming skill: do not rename util to helperssplit it by responsibility. Each cluster of functions that shares a purpose becomes its own small, well-named package.

// WRONG — one grab-bag, growing without bound
package util
func ParseDuration(s string) (time.Duration, error) { /* ... */ }
func Slugify(s string) string                        { /* ... */ }
func RetryHTTP(req *http.Request) (*http.Response, error) { /* ... */ }

// RIGHT — split by what each does; the caller's import says what it gets
package timeparse  // timeparse.Duration(s)
package slug       // slug.Make(s)
package httpretry  // httpretry.Do(req)

The package name rule (no util, no stutter) is owned by go-naming-and-style; this skill owns the decomposition that fixes the grab-bag. Google's guide states the ban plainly: "Naming a package just util, helper, common or similar is usually a poor choice" (Google Best Practices).


7. The "Standard Project Layout" Myth, and pkg/

There is no official, blessed Go project layout beyond the minimal [go.dev/doc/modules/layout]. The popular golang-standards/project-layout GitHub repository is a community project, not a Go-team standard — despite its name and star count, the Go maintainers do not endorse it, and applying its deep pkg/ internal/ api/ build/ deployments/ tree to a small program is exactly the premature over-structuring of Section 2.

pkg/ specifically is not required and not recommended as a reflex. The official layout document never mentions a pkg/ directory; it uses the module root, internal/, and cmd/. A top-level pkg/ adds an import-path segment (example.com/proj/pkg/foo instead of example.com/proj/foo) and a directory level for no enforcement benefit — internal/ is the directory that actually does something (Section 4). Reach for pkg/ only if you have a concrete, articulated reason; default to placing exported packages at the module root and private ones under internal/.

When asked "do I need a pkg/ folder?" the answer is almost always no — point to [go.dev/doc/modules/layout] and start flat.


8. Who Suffers When Layout Is Done Badly

The cost of bad structure is paid by the reader navigating the tree, never by the author who scaffolded it:

  • The new teammate who clones a 300-line tool and finds pkg/, internal/, api/, models/, services/, and util/ — six directories implementing what one flat package would hold — and has to open all of them to find where anything happens.
  • The maintainer of a util package that started with one function and now drags in a database driver, an HTTP client, and a YAML parser as transitive dependencies, slowing every build that imports it (Package Names).
  • The reviewer of a feature change that touches models/x.go, services/x.go, and controllers/x.go in three separate diffs because the layer split scattered one capability across the tree.
  • The caller who hits an import cycle — services imports models imports services — that is really the compiler reporting a wrong package boundary, and now has to redraw it under deadline.

"A little copying is better than a little dependency" (Go Proverbs) and "Clear is better than clever" are layout rules too: the simplest tree that names each part for its job is the one nobody downstream has to decode.


9. Routing to the Specific Skills

  • go-idiomatic-discipline — the policy root. Over-structuring a small program (deep pkg//internal/ tree, util grab-bag) is its axis 2; this skill holds the structural depth.
  • go-naming-and-style — owns the package name rules (short lowercase noun, no util, no stutter). This skill owns the boundary and the decomposition; naming owns what the boundary is called.
  • go-modules-and-versioning — owns go.mod/go.sum, the module that contains the layout, and is the root that internal/ privacy is measured from.
  • go-interfaces — consumer-side interface placement shapes package boundaries and keeps the dependency arrows acyclic; the interface design lives there.
  • go-tooling-and-static-analysisgofmt/go vet/golangci-lint, the CI gate that runs over whatever tree you build.

10. Reference Files

High-frequency layout anti-patterns in LLM-generated Go, each with wrong/right directory trees and citations:

${CLAUDE_SKILL_DIR}/references/common-mistakes.md

Source provenance for every claim in this skill:

${CLAUDE_SKILL_DIR}/references/sources.yaml

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.