Skip to content

Latest commit

 

History

History
255 lines (214 loc) · 11.9 KB

repo-layout.md

File metadata and controls

255 lines (214 loc) · 11.9 KB

Working with Repository Layout

This document is the family-wide standard for the static shape of a CLI repository — directory structure, the files it must contain, the Go-version policy, the Makefile contract, the lint config, and the repository settings. It is the foundation the other repo-axis docs build on: ci.md, release.md, and distribution.md all consume primitives defined here.

This is normative for new CLIs.

Companion pillars:

  • ci.md — invokes the Makefile targets (§4) and lint config (§5) defined here.
  • release.md — reads version.txt (§2) and the commit conventions (§7).
  • distribution.md — reads .goreleaser.* (§2).
  • agent-implementation.md — owns how agent entrypoint files are used; this doc owns that the files exist in the repo skeleton.
  • The behavior-axis docs (command-surface.md, output-and-rendering.md, working-with-secrets.md, working-with-state.md, scriptability.md) govern what lives inside the directories below. This doc owns the skeleton; they own the contents.

repo-layout.md is foundational among the repo-axis docs — when another repo-axis doc appears to conflict on a primitive defined here (Go version, Makefile targets, lint config, version.txt), this doc wins. See docs/README.md for the full conflict-resolution order.


§1 Directory layout

cmd/<binary>/main.go        # entry point — one per shipped binary
internal/
  cmd/
    root/                   # root command + Options struct (the DI seam)
    initcmd/                # <tool> init        (scriptability.md §1)
    setcred/                # <tool> set-credential (working-with-secrets.md §1.5.2)
    config/                 # <tool> config {show,clear,test}
    me/                     # <tool> me          (scriptability.md §4)
    <feature>/              # one package per domain command group
  config/                   # config-file load/save (working-with-state.md §3)
  client/                   # the data layer — API client (output-and-rendering.md §9)
  output/  (or view/)       # the presentation layer — pure render fns
  version/                  # version string wiring (release.md §2)
  noleak/                   # secret no-leak test helper (working-with-secrets.md)
  testutil/                 # shared test fixtures
api/                        # OPTIONAL: exported public client library

This is the shape slck (the cli-common pilot) and nrq follow. A new CLI starts from this tree. The three-layer seam — client (data) → output (presentation) → internal/cmd (command glue) — is mandated by output-and-rendering.md §9; this section only fixes where those layers live.


§2 Required files

File Status Notes
README.md MUST install + usage
LICENSE MUST MIT
AGENTS.md MUST thin agent entrypoint index; peer of CLAUDE.md
CLAUDE.md MUST thin agent entrypoint index; peer of AGENTS.md
docs/development.md MUST repo-local facts; tool-local equivalent allowed in monorepos
.golangci.yml MUST §5
Makefile MUST §4
version.txt MUST release.md §2 source-of-truth
.gitignore / .gitattributes MUST
.goreleaser.{yml,yaml} MUST if distributed distribution.md §1
packaging/identity.yml MUST if distributed distribution.md §8 — required by identity-check
CONTRIBUTING.md SHOULD
CHANGELOG.md SHOULD release-generated

Agent entrypoints are indexes, not containers for copied policy. Keep repo-specific facts in docs/development.md; keep shared standards in cli-common/docs; keep shared automation in open-cli-collective/.github. See agent-implementation.md for the full source-of-truth and reference-link policy.

Monorepo (atlassian-cli): per-tool files live under tools/<tool>/ (version.txt, .goreleaser-<tool>.yml); repo-root carries the shared go.work, LICENSE, and CI. Tool-local agent entrypoints and docs/development.md equivalents are allowed where they reduce navigation cost.

§2.1 Library repos (no shipped binary)

A repo that ships no binary and publishes through no package channel — cli-common itself — follows this doc under a reduced profile:

  • Required files: README.md, LICENSE, AGENTS.md, CLAUDE.md, docs/development.md, .golangci.yml, Makefile, .gitignore/.gitattributes. Agent entrypoints are required in every repo agents work in, library or not.
  • Not required: version.txt, .goreleaser.*, packaging/identity.yml, CHANGELOG.md — distribution files exist only where something ships.
  • Makefile: the §4 library exemption applies, with check = tidy + lint + test + build so a green local check still predicts a green CI run.
  • Release/tagging: semver tags are cut manually. release.md's auto-release and run-number patch scheme do NOT apply — a library with co-evolving consumers must gate its tags on the consumer matrix being green against the candidate SHA, and automating the tag would defeat that gate. (working-with-state.md §6 is the concrete instance of this rule for cli-common's state components.)
  • CI: ci.md applies — build/test/lint across the three OSes, go-version-file: go.mod, and pr-title on the same terms as every repo (i.e., when the shared composite ships in the family rollout, ci.md §8). identity-check is N/A (nothing ships). This profile creates no new CI-restructuring obligation beyond ci.md's existing rules.

§3 Go version policy

  • The floor is Go 1.26. New repos pin the floor.
  • The single source is the go directive in go.mod. Everything else (ci.md §3, local dev) reads from there. Do not duplicate the version into a workflow literal that can drift.
  • Bump across the family in lockstep. salesforce-cli is on 1.24; hubspot-cli declares 1.23.0 in go.mod but pins 1.22 in CI (a mismatch — sync both) (§8).
  • Monorepo: each tool has its own go.mod; all sit on the floor.

§4 Makefile target contract

CI and release invoke the Makefile, so the target names are a contract. Every repo's Makefile MUST provide:

Target Does
build compile the binary/binaries
test go test ./...
test-cover tests with coverage profile
lint golangci-lint run
fmt gofmt/goimports
tidy go mod tidy
deps fetch/verify deps
check tidy + fmt + lint + test + build — the local pre-push gate
install install to a local bin
release / snapshot goreleaser wrappers
clean remove build artifacts

make check MUST mirror what CI gates on, so a green local check predicts a green CI run.

Library/shared-module exemption. The full target set above applies to shipped-binary CLI repos. A library or shared module that produces no binary and ships through no package channel — e.g. cli-common itself (§2.1) — needs only tidy/lint/test/build, with check running all four, and is exempt from install/release/snapshot/test-cover. Do not force distribution targets onto a repo that distributes nothing.


§5 Lint configuration

.golangci.yml, format version: "2". The canonical floor linter set every repo MUST enable:

errcheck  govet  ineffassign  staticcheck  unused  misspell  revive  gosec  errorlint  exhaustive

This is already the de-facto family set — google-readonly and all three atlassian-cli configs (shared/, tools/cfl, tools/jtk) ship it. gosec and errorlint matter for CLIs that handle credentials, which is all of them. Tests are excluded from errcheck (and from errorlint/gosec where noisy). A repo MAY add more linters but MUST NOT drop below the floor; the leaner repos (slack-chat-api, salesforce-cli) sit below it today (§8). Running on golangci defaults (no config file) is non-conformant.


§6 Repository settings

Branch protection (main)

  • Require a PR with 1 approval.
  • Required status checks: build, test, lint, pr-title, and identity-check (ci.md §2). pr-title is required because it gates release eligibility (release.md §1); identity-check because it guards the single-source manifest (distribution.md §8.2). build is the required aggregate over the non-required build-platform OS matrix (ci.md §2/§7) — require build, never the per-OS legs (their names shift with the matrix). Current repos require only test + lint — fold the rest in when migrating. Because CI is shared via composite actions (ci.md §7), these are bare check names and stay bare on migration — no branch-protection rewrite (the context-rename hazard only applies to the reusable-workflow form, which CI does not use).
  • Sequencing (load-bearing): add a required check to branch protection only after the workflow that produces it exists and has run green on the repo — pr-title after the composite is wired, identity-check after the composite ships and packaging/identity.yml exists. A required check that no workflow ever reports stays permanently pending and silently blocks every PR.
  • Require signed commits.
  • Require linear history.
  • No force pushes.

Merge settings

  • Squash merge only (no merge commits, no rebase merge). This is why the PR title must be a conventional commit (release.md §1).
  • Set squash_merge_commit_title=PR_TITLE and squash_merge_commit_message=PR_BODY. COMMIT_OR_PR_TITLE is prohibited: for a one-commit squash PR it can select the lone commit subject instead of the validated PR title and bypass the pr-title check. PR_BODY keeps the validated PR body as the squash commit body.
  • Delete branch on merge.

§7 Commit hygiene

  • Conventional commits drive releases (release.md §1).

  • Commit messages MUST NOT mention AI tooling (Claude, Anthropic, ChatGPT, Copilot, etc.). Enforce with a commit-msg hook that greps a blocklist and rejects on match. The hook alone is insufficient under squash merge: the required PR_TITLE / PR_BODY settings build the landing commit from the PR title and body, neither of which the local hook sees. The CI pr-title check therefore greps both against the same blocklist (ci.md §2). Reference implementation (track it as scripts/hooks/commit-msg and wire via git config core.hooksPath scripts/hooks):

    #!/bin/sh
    if grep -iEq 'claude|anthropic|chatgpt|copilot|codex' "$1"; then
      echo "commit message references AI tooling — remove it (repo-layout.md §7)" >&2
      exit 1
    fi

§8 Current divergences

  • .golangci.yml missing in codereview-cli only — it is the single repo without one. atlassian-cli is not missing it: as a go.work monorepo it carries three configs (shared/, tools/cfl, tools/jtk), all on the §5 set.
  • Linter floor not yet met by slack-chat-api and salesforce-cli (lean 6-linter core, missing revive/gosec/errorlint/exhaustive) (§5).
  • Agent guidance entrypoints missing in hubspot-cli and salesforce-cli: both have CLAUDE.md but lack AGENTS.md and docs/development.md (§2).
  • CONTRIBUTING.md / CHANGELOG.md gaps in google-readonly, salesforce-cli, hubspot-cli, and atlassian-cli root (§2).
  • Go versions: hubspot-cli declares go 1.23.0 in go.mod but its CI pins 1.22 (an internal mismatch — sync both); salesforce-cli is on 1.24; all others on 1.26 (§3).
  • Required-check names: newrelic-cli and codereview-cli branch protection requires only test + lint (no build, no pr-title, no identity-check). Composite-based CI (ci.md §7) keeps these bare, so migration just adds the missing checks — no renaming of the existing ones (§6).
  • Makefile target coverage varies; google-readonly alone has test-cover-check. cli-common is intentionally minimal (library exemption, §4).