Skip to content

commit-check — link every governed change to its decision

decree commit-check gates the commit→decision link itself. git guarantees what changed; the Implements:/Refs:/Fixes: trailer that says which decision a change implements is an unenforced convention. commit-check measures whether that link is present — and lets CI require it.

Scenario A change to src/auth/tokens.py (governed by an in-flight SPEC) is about to merge.

Without decree

The commit lands with no trailer. git records the diff, but nothing links it to the decision it implements. The “why” is lostdecree refs shows the SPEC with zero implementing commits, and health can’t even flag it (it’s “unobserved, not dead”). The provenance silently rots.

With decree

CI runs decree commit-check --diff-base origin/main --strict on the net diff and fails when a governed change has no matching trailer:

Terminal window
$ decree commit-check --diff-base HEAD~1 --strict
[commit-check] exit 1: trailer coverage below threshold.
Commit check — diff-base
Trailer coverage (0/1, 0%)
Uncovered (1):
• src/auth/tokens.py → SPEC-00000000000000000000000001 (JWT token storage)
Consider `decree commit --implements SPEC-00000000000000000000000001` so the commit links to the SPEC.
→ exit 1

VALUE The weakest link in decree’s provenance — the commit→decision trailer — becomes a check CI can require, at the one moment the decision is knowable.

HONESTY It is coverage you can gate, not a guarantee: git commit --no-verify and CI overrides exist, so it measures and enforces where you run it — it cannot make the link true. It reads only declared governs:; a ticket is not a decision and decree never maps one.

commit-check intersects a diff’s changed paths with the declared governs: map (via why, the authoritative layer — never git history as truth), keeping only in-flight decisions. For each governed change it checks whether the commit message(s) carry a matching Implements:/Refs:/Fixes: trailer. The primary mode is --diff-base origin/main: it gathers trailers across the whole commit range, so it survives squash-merge. It is advisory by default--strict (require 100%) or --min-coverage N (ratchet up over time on a legacy repo) turn an uncovered change into a finding.

Under --strict, a governed change with no linking trailer fails:

Terminal window
$ decree commit-check --diff-base HEAD~1 --strict
[commit-check] exit 1: trailer coverage below threshold.
Commit check — diff-base
Trailer coverage (0/1, 0%)
Uncovered (1):
• src/auth/tokens.py → SPEC-00000000000000000000000001 (JWT token storage)
Consider `decree commit --implements SPEC-00000000000000000000000001` so the commit links to the SPEC.
→ exit 1

The same check is exposed over MCP as the commit_check tool, with the identical JSON payload, so an agent gates its own loop.

  • As a CI gate on every PR — require that governed changes name the decision they implement (--strict), so provenance can’t silently rot.
  • In an agent loop — the actor most likely to drop a trailer; gate it deterministically.
  • Adopting on a legacy repo — start with --min-coverage at today’s number and ratchet up, no flag day.

Advisory by default: it exits exit 0 clean · advisory-only and reports unless you opt into a gate. With --strict or --min-coverage, an uncovered governed change exits exit 1 a finding you can gate CI on . A config error (missing index, no trailer source) exits exit 2 config error .

All flags

| Flag | What it does | |------|--------------| | --diff-base REF | CI mode: paths from git diff REF...HEAD, trailers unioned across the commit range (squash-safe). | | --diff PATH | A unified diff file (- = stdin); pair with --message. | | --message PATH | The candidate commit message (for a commit-msg hook). | | --strict | Require 100% coverage; any uncovered governed change exits 1. | | --min-coverage N | Require ≥ N% coverage (ratchet for gradual adoption). | | --json | Stable machine payload (same as the MCP tool). | | --project PATH | Operate on the project at this path. |

decree does not install a git hook (that’s the harness’s job). To enforce locally, opt in with a one-line commit-msg hook calling decree commit-check --message "$1" --strict.


Next: health — has a decision’s declared scope rotted over time? Or see how decree relates to git and session-capture tools.