rossedman/canon

Your standards,
as agent tools.

canon turns decision records into an MCP server, a CLI and a hook, so your coding agent checks its work and asks "how do we do X". Every finding says why.

$ go install github.com/rossedman/canon@latest
~/repo
$ canon check
Dockerfile:6 error Final stage uses debian:bookworm
canon-final-stage-base → containers-distroless-base
Runtime images carry no shell or package manager.
fix: FROM gcr.io/distroless/static-debian12:nonroot
$ canon profile
profile go-cli
19 records in force
  • 25decision records
  • 21rules mapped to records
  • 6MCP tools
  • 7checkers, linters included

The problem

Agents guess.
Your standards don't.

Standards live in wikis and review comments, so an agent writing code at 2am never reads them. canon puts each one behind a tool the agent calls mid-task, and the answer always carries the reasoning.

  • Records are the single source of truth
  • Machines check, people decide
  • Exceptions are written down, not guessed
canon hookerror exit 2, agent keeps working

Final stage sets no USER, so it runs as root

Dockerfile:6 · canon-final-stage-user → containers-non-root
A process that escapes a non-root container starts with no privileges on the host.the record's summary, returned as the finding's why
fix: USER 65532:65532

Core concepts

Records, rules, profiles.
Nothing else to learn.

A record says what you decided and why. A rule maps a linter's output to a record. A profile says which records apply to which kind of repo.

Record

Markdown + YAML frontmatter

The decision, its context and its exceptions. The summary becomes the "why" in findings.

id: go-errors-wrap
status: accepted
strength: must
rules: [golangci-errors]

Rule

canon/rules/rules.yaml

Ties a checker's codes to a record. A finding without a rule is never reported.

- id: golangci-errors
  record: go-errors-wrap
  checker: golangci-lint
  codes: [wrapcheck, errorlint]

Profile

canon/profiles/*.yaml

Detects the kind of repo and selects the scopes of records in force.

name: go-cli
detect:
  all: [go.mod, cmd/]
scopes: [go, containers, ci, oss, yaml]

Why canon

Opinions with receipts.
Checked, not nagged.

Four front doors, one engine

MCP server, CLI, Claude Code hook and CI all run the same checks, so the agent, the laptop and the pipeline agree.

Every finding says why

Each finding carries its rule, the record it enforces and the reason, with a fix when one is known.

Wraps the linters you know

golangci-lint, ruff, hadolint, tflint, checkov and yamllint, plus canon's own checks for what they miss.

Knows your repo

Six profiles detect go-cli, go-service, go-library, py-service, tf-module or generic, and pick the records in force.

Starts projects right

Six scaffold kinds. Dry run by default, never overwrites, and the result is checked before it is reported.

Releases you can verify

Archives ship with a keyless cosign signature over checksums.txt, tied to the release workflow.

Quick start

Standards in your agent
in three commands.

You need Go 1.27 or newer to install from source. Linters are optional: canon reports which are missing and skips them.

Full getting-started guide
  1. Install

    $ go install github.com/rossedman/canon@latest
  2. Give Claude Code the tools

    Registers the MCP server over stdio.

    $ claude mcp add canon -- canon serve
  3. Run your first check

    From the root of any repository.

    $ canon check

How it works

One canon in.
Four doors out.

Decision records, rules and profiles feed one engine, which runs the checkers and is exposed as an MCP server, a CLI, a Claude Code hook and a CI check that uploads SARIF.
Tap to open the full-size diagram

Write it down once.
Let the agent follow it.

Free and open source under the Apache-2.0 license.