musher-dev/engineering-conventions is the authoritative home for Musher's shared engineering language, repository
structures and implementation expectations. It defines the conventions, publishes the checks that validate them, and
releases both as a versioned bundle that other repositories pin. It does not run application code, and it does not
host CI for other repositories.
Status: v0, draft. Every requirement is
proposedat severitywarning: a finding is advice, not a failure. The GitHub Actions conventions are derived from checks owned bymusher-dev/platform, which stays their authority until a single handoff moves them here; where they differ from the platform, the difference is a proposal it adopts then (decision 0002).
A convention is a published, versioned decision about how one part of a repository is named, structured or configured, written as requirements with permanent IDs that tools can check and people can cite.
Each piece has one job:
| Piece | Example | What it is |
|---|---|---|
| Topic | github-actions |
The part of a repository the conventions govern, named for its files or tool |
| Convention | EC-0002 Workflow files |
A document that states related requirements and explains why |
| Requirement | GHA-07 |
One normative statement, with an ID that never changes or gets reused |
| Check | a Rego policy | Code that finds violations of a requirement |
| Diagnostic | warning [GHA-07] … |
A finding in your repository, linking back to the requirement's heading |
For example, GHA-07 says a workflow's name is its filename stem in Title Case. A repository whose
.github/workflows/validate.yml says name: CI gets:
GHA-07 warning 1 finding
A workflow's name is its filename stem in Title Case
https://github.com/musher-dev/engineering-conventions/blob/vX.Y.Z/engineering-conventions/definitions/conventions/github-actions/workflow-files.md#gha-07
.github/workflows/validate.yml
name "CI" should be "Validate": a workflow's name is its filename stem in Title Case
If it helps, think of a building code:
| Building code | Here |
|---|---|
| The code book | The conventions |
| A numbered section of the code | A requirement, such as GHA-07 |
| The inspector's checklist | The checks |
| The class of building (house, warehouse) | A convention profile, such as base-repo |
| The edition a permit cites | The release a repository pins |
| A granted variance, with an end date | A waiver in .repo/conventions.toml |
A convention is not a template you copy (nothing here is applied to your repository), not a matter of taste (each requirement names the failure it prevents), and not a definition of a word that another repository owns.
What is decided lives in definitions/. What checks it lives in checks/. Both ship as one release, and each
repository pins a release and checks itself against it.
flowchart LR
subgraph defined["Defined here: definitions/"]
conventions["Conventions and requirements<br/>EC-0002, GHA-07"]
terminology["Terminology"]
profiles["Profiles"]
end
subgraph checked["Checked here: checks/"]
rego["Rego policies"]
schemas["JSON Schemas"]
generated["index.json, Vale style<br/>(generated)"]
end
release[["Release vX.Y.Z<br/>bundle, checksums, attestation"]]
subgraph yours["Your repository"]
pin[".config/mise/config.toml pin"]
declarations[".repo/conventions.toml<br/>.repo/outputs.toml<br/>.repo/repository.toml"]
check["conventions check"]
end
upstream["Defined elsewhere<br/>platform, specifications,<br/>observability-schema-registry"]
defined -- "task generate" --> generated
generated --> rego
defined --> release
checked --> release
release -- "pinned by" --> pin
pin --> check
declarations --> check
check -. "diagnostic links to ### GHA-07" .-> conventions
upstream -. "authority pointer" .-> terminology
Inside the product directory, engineering-conventions/, the layers are
directories: definitions/ is decided, checks/ validates, examples/ shows a conforming repository, and bin/ and
src/ are the tooling that runs the checks. How this repository is organized says where every
file goes.
Start from the part of your repository you are working on:
| Topic | Governs in your repository | Conventions | Requirements |
|---|---|---|---|
| Adoption | The release pin in .config/mise/config.toml, .repo/conventions.toml |
EC-0001 | ADOPT-01 – ADOPT-11 |
| Agents | CLAUDE.md, .claude/rules/, AGENTS.md |
EC-0013 | AGENT-01 – AGENT-08 |
| Configuration | .config/, tool configuration at the root, scanner ignore files |
EC-0011, EC-0012 | CONF-01 – CONF-12 |
| Decisions | docs/decisions/, the decision records |
EC-0021 | DEC-01 – DEC-07 |
| Dev containers | .devcontainer/: devcontainer.json, its lockfile, its Dockerfile and compose stacks |
EC-0027, EC-0028 | DEVC-01 – DEVC-13 |
| Environment | <product>/env.schema.yaml, .devcontainer/env.schema.yaml |
EC-0019, EC-0020 | ENVS-01 – ENVS-15 |
| Git hooks | .config/lefthook.yml |
EC-0014 | HOOKS-01 – HOOKS-11 |
| GitHub Actions | .github/workflows/, .github/actions/ |
EC-0002 – EC-0006, EC-0022, EC-0023 | GHA-01 – GHA-49 |
| Outputs | .repo/outputs.toml, publish workflows |
EC-0007, EC-0008 | OUT-01 – OUT-12 |
| Releases | .github/release-please/, the release tag ruleset, release.yml |
EC-0024 – EC-0026 | REL-01 – REL-20 |
| Repository | .repo/repository.toml, the repository's name, where the product lives |
EC-0009, EC-0010, EC-0018 | REPO-01 – REPO-22 |
| Tasks | Taskfile.yml, taskfiles/ |
EC-0015, EC-0016 | TASK-01 – TASK-14 |
| Toolchain | .config/mise/config.toml and every version pinned outside it |
EC-0017 | TOOL-01 – TOOL-11 |
A topic's README gives the reading order. Two other ways in:
- By ID. A diagnostic names its requirement and links to the
### <ID>heading, its permanent address. The generated index of every ID lists every one ever issued, retired ones included. - By machine.
engineering-conventions/checks/data/index.jsonholds every requirement with its title, status, severity, convention, path and anchor, plus the profiles and the vocabulary.
The rule of thumb: it owns the rules about things, not the things. Something can live here when many repositories need the same answer and a check can cite it by ID. One repository's own rule stays in that repository, and a word or format another repository defines gets a pointer, not a copy.
| Owned here | Not owned here |
|---|---|
That a workflow that publishes is named publish[-<scope>].yml (GHA-02) |
Your publish workflow, what it builds, when it runs |
The format of .repo/outputs.toml and what each output states (EC-0007) |
The image, its registry, or a catalog that collects declarations |
The repository-name grammar <system>-<component>, the registered systems, and .repo/repository.toml (EC-0009, EC-0010) |
Which teams exist and who is in them; applying names and properties to the organization |
validate as a workflow token, and its banned synonyms ci and checks |
Platform domain nouns, telemetry names, the customer glossary |
| The checks, and the release that ships them | Running them: your CI runs conventions check |
| What a waiver must state and how long it may last | Whether your repository needs one |
Where the rest lives:
| Not owned here | Owned by |
|---|---|
| How the company operates: rituals, planning, the operating model | musher-dev/company |
| Telemetry names: spans, metrics, attributes, events | musher-dev/observability-schema-registry |
| Document specifications: the component, blueprint and catalog formats | musher-dev/specifications |
| Positioning, product vocabulary, the customer glossary, platform domain nouns | musher-dev/platform and musher-dev/company |
| The development-container scaffold: the container, its toolchain setup, its stacks | musher-dev/development-container |
| Teams and their membership | musher-dev/company, applied by musher-dev/infra-github |
| Applying repository names, custom properties and rulesets to the organization | musher-dev/infra-github |
| Rules local to one repository | That repository |
A repository's own rule may be stricter than a convention here, never contradictory. The charter is the authority for this boundary.
Pin a release in .config/mise/config.toml and run one command, the same locally and in CI:
min_version = "2026.9.12"
[tools]
"github:musher-dev/engineering-conventions" = "0.6.3" # x-release-please-versionconventions checkIt prints a report grouped by requirement; --output json prints the findings for a program to read. mise verifies
the release's checksum and build provenance, and Renovate raises the pin. To try it without changing anything:
mise exec github:musher-dev/engineering-conventions@0.6.3 -- conventions check # x-release-please-versionThe details, and the path without mise, are in Consuming the conventions.
| Read | When you want to |
|---|---|
| Consuming the conventions | adopt a release, declare outputs, read a diagnostic, add a waiver |
| Authoring conventions | propose or change a requirement or a term |
| Versioning | know what a release number promises |
| How this repository is organized | know where a file goes |
| Decisions | understand why the repository works the way it does |
Open the repository in its dev container (or install the pinned tools with mise), then:
task setup # install the pinned toolchain, the authoring CLI and the git hooks
task check # every gate CI runs, except the dev container buildTo add or change a requirement, read Authoring conventions and CONTRIBUTING.md.
Releases are cut by release-please from Conventional Commit titles. Each vX.Y.Z release attaches the bundle
tarball, the MusherConventions Vale package, a manifest, SHA256SUMS, and a build-provenance attestation. The
version number says what an upgrade can break; see Versioning.
See SECURITY.md to report a vulnerability.