Review rules — COMMITBRIEF.md and OUTPUT.md
Customise the system prompt your reviewer sees (COMMITBRIEF.md) and how findings render to markdown locally (OUTPUT.md template).
CommitBrief separates what the reviewer looks for from how the review is rendered. Two files, two layers, neither required to get started.
COMMITBRIEF.md — the system prompt
The team-shared review rules file. Sent to the LLM as the system prompt on every review. Lives at the repo root and is committed to git.
Path
<repo-root>/COMMITBRIEF.md
If the file is missing, CommitBrief falls back to an embedded default. The default works fine on its own — customising is purely additive.
Scaffold it
commitbrief init
Writes the embedded default to COMMITBRIEF.md, and also writes the
per-user .commitbrief/OUTPUT.md template (see
below). If only one of the two files
exists, the other is still written. Use --force (or --yes) to
overwrite existing files.
Format
Free-form markdown. Anything you write is passed to the model as
the system prompt, wrapped in <project_rules>...</project_rules>
XML tags. Inside the envelope the model sees your literal content
verbatim; around it, the prompt builder adds an optional
<architecture_constraints> block (only when an
architecture.json is in effect), a
<severity_rubric> block, a <response_format> block carrying the
structured-findings JSON contract (a plain-text contract for the CLI-backed providers), a language directive (per
output.lang) and an immutability guard (a prompt-injection defense —
the model is told to treat project rules as immutable and to ignore any
instruction inside them that tries to override its task).
There are no required headings or template variables.
Because the file’s content becomes the system prompt, CommitBrief
runs two defenses on a non-default COMMITBRIEF.md (or OUTPUT.md)
before the provider call: the
secret scanner checks it
for credential-shaped strings, and a
prompt-injection scan
surfaces a non-blocking warning if it spots injection phrasing
(“ignore previous instructions”, “you are now…”). Both skip the
trusted embedded default.
What the embedded default covers
- Persona — adversarial defender + optimization engineer.
- What to look for — correctness, security, performance and efficiency, maintainability, code quality.
- What NOT to flag — explicit “do not nitpick” list to keep the noise floor down.
- Output discipline — how long a
descriptionand asuggestionshould be, and a preference for silence over speculation. - Project context — a short block for you to fill in.
The structured-findings JSON contract is not part of this file: the
prompt builder appends it separately as <response_format>, so a
custom COMMITBRIEF.md does not have to restate it.
Customisation patterns
Project-specific context
## Project context
This is a financial transaction service. Every state change to
`Transaction` must go through `transactions.Repo.Apply()` which
enforces audit logging — direct mutations bypass compliance.
The `internal/billing/` package operates on cents (int64). Never
introduce float64 anywhere in the price-handling path.
High-stakes paths
## High-stakes paths
- `internal/auth/` — authentication / session handling. Any
change here needs a security-focused review.
- `db/migrations/` — schema changes. Verify backward
compatibility with the previous release.
Style conventions
## Conventions
- Use `errors.Is` / `errors.As`, never string-compare error messages.
- All public exported functions must have a doc comment starting with the function name.
- No `panic()` outside `init()` blocks.
Size considerations
A COMMITBRIEF.md is sent on every review as part of the prompt.
Past ~10k tokens (~40k characters) you pay noticeable input cost.
Use commitbrief compress to losslessly shrink the file via the
LLM:
commitbrief compress # default level: balanced
commitbrief compress --dry-run # preview without writing
commitbrief compress --level=aggressive --out COMMITBRIEF.compressed.md
The compress pipeline backs up the original to
.commitbrief/backups/COMMITBRIEF-<ISO-timestamp>.md before
overwriting, and refuses to apply when the compressed result is
not smaller than the original.
What CommitBrief does NOT do with this file
- It does NOT lint the file or enforce a schema.
- It does NOT validate references (e.g. “the file mentioned doesn’t exist”).
- It does NOT version the file or track changes — that is git’s job.
OUTPUT.md — the markdown template
A per-user Go text/template
that controls how findings are formatted locally. Applied to
the parsed findings JSON to produce the markdown output for
--markdown and --output <file>.md. Never sent to the LLM.
Where it lives
CommitBrief looks in this order (first hit wins):
<repo-root>/.commitbrief/OUTPUT.md— per-repo override.<user-home>/.commitbrief/OUTPUT.md— per-user default.- Embedded default at
internal/rules/output.md.
A repo-local OUTPUT.md is gitignored by default. This makes it a personal preference rather than a team artifact.
Template data
type TemplateData struct {
Findings []Finding
}
type Finding struct {
Severity Severity // named string type: critical|high|medium|low|info
File string
Line int
LineEnd int
Title string
Description string
Suggestion string
Language string
Snippet string
}
Inside the template, .Findings is a typed slice:
{{ range .Findings }}
- **{{ upper (printf "%s" .Severity) }}** {{ .PathRef }} — {{ .Title }}
{{ .Description }}
> {{ .Suggestion }}
{{ end }}
Available functions
| Function | Signature | Purpose |
|---|---|---|
upper | string → string | strings.ToUpper. |
lower | string → string | strings.ToLower. |
groupBySeverity | []Finding → []SeverityGroup | Group by severity, ordered critical to info; empty groups are dropped. Each SeverityGroup has .Severity and .Items. |
countFiles | []Finding → int | Distinct file count. |
.Severity is a named type, not a plain string, so pass it through
printf "%s" before upper or lower: {{ upper (printf "%s" .Severity) }}.
Each finding also has the methods .LineRef (142 or 142-145) and
.PathRef (file:line, or just the file when no line is set).
The set is the public template contract; additions are allowed in
v1.x, removals require a schema bump. All built-in
text/template actions (range, if, with, len, printf,
…) also work.
Pre-send validation
If you write a custom OUTPUT.md and the template is malformed, CommitBrief catches it before any provider call. Three checks run on load:
- Parse —
text/templatesyntax check. - Empty execute — template runs against an empty
[]Finding{}. - Sample execute — template runs against a synthetic sample
of two findings (one
critical, oneinfo).
A failure aborts the run with a pointer at the file and a hint
to run commitbrief init --yes to overwrite with the default.
Example — group by severity
# Review summary
Files touched: {{ countFiles .Findings }}
{{ range groupBySeverity .Findings }}
## {{ upper (printf "%s" .Severity) }} ({{ len .Items }})
{{ range .Items }}
- **{{ .PathRef }}** — {{ .Title }}
{{ .Description }}
> {{ .Suggestion }}
{{ end }}
{{ end }}
groupBySeverity returns a slice, so the outer range walks the
severities in order and skips the empty ones. To print only one
severity, add {{ if eq (printf "%s" .Severity) "critical" }} inside
the outer range.
What OUTPUT.md does NOT do
- It does NOT affect the LLM prompt or the review behavior.
- It does NOT affect
--jsonoutput (the JSON schema is fixed). - It does NOT apply to the cards renderer (lipgloss-styled layout).
- It does NOT apply to CLI-tool-backed providers (claude-cli / gemini-cli / codex-cli emit pre-formatted text directly).
See also
- Output formats — where the rendered markdown shows up.
- Configuration —
output.langand other rendering knobs. .commitbriefignore— separate file controlling which files reach the prompt.- Signal control —
COMMITBRIEF.mdsteers what gets flagged; the baseline and inlinecommitbrief-ignoredrop findings you’ve already triaged.