Expand description
The commit as a value: one grammar, one policy, one verdict, one plan.
§What was here before
Two halves of this subject already existed and did not know about each other. The
changelog parsed conventional commits out of the history to render a release; the rule
project.conventional-commits stated what a commit must look like and was marked
advisory, with the failure behaviour “no command decides this rule; a reviewer does”.
So the repository had a parser and a rule, and no verdict — the exact shape of governance
the rest of this repository exists to refuse.
The other half was outside the repository altogether. The procedure for writing a commit — how to derive a scope, how to link work, when a fix needs a test, which subjects are exempt — lived in prose that an AI client read and executed by hand, one file per client, with a path-to-scope table maintained by a person. That is a workflow whose semantics live in a prompt, which makes it unversioned, unenforceable, untestable and true for exactly one tool.
§What this is
One typed subject with four parts, each the only place its question is answered:
CommitHeaderandCommitMessage— the grammar. The changelog and the validator read this parse; there is no second regular expression anywhere.CommitPolicy— what this repository holds a commit to, as data inpolicy.yaml.judge— the verdict, as typed findings over the canonicalcrate::model::Diagnostic.planandscopes— what is in the working tree, which scope the history gives it, and how it divides into commits, fingerprinted against the tree it came from.
Every surface is a projection of these: the capabilities in
crate::capability::builtin::commit carry them to MCP and HTTP, the command line
renders them, the commit-msg hook asks judge the same question CI asks of the
history, and the documentation is generated from the declarations. Nothing restates them.
use majordomus_cli::commit::{judge, CommitPolicy, CommitHeader, CommitMessage, CommitSubject};
// one grammar
assert!(CommitHeader::parse("feat(commit): the subject is a value").is_conventional());
// one verdict, from the same parse
assert!(judge(&CommitSubject::of("feat(commit): the subject is a value"), &CommitPolicy::default()).passed);
// and a message is a value that renders, not a string that is parsed back
assert_eq!(CommitMessage::parse("docs: one line").render(), "docs: one line\n");Re-exports§
pub use header::CommitHeader;pub use message::CommitMessage;pub use message::CommitTrailer;pub use plan::ChangeStage;pub use plan::CommitGroup;pub use plan::CommitPlan;pub use plan::PlanFingerprint;pub use plan::WorkingTreeState;pub use policy::CommitPolicy;pub use policy::FindingLevel;pub use scopes::ScopeSuggestion;pub use scopes::ScopeVocabulary;pub use verdict::judge;pub use verdict::CommitExemption;pub use verdict::CommitRule;pub use verdict::CommitSubject;pub use verdict::CommitVerdict;
Modules§
- header
- The conventional-commit grammar: one parser, one renderer, one place.
- message
- A whole commit message: the header, the body, and the trailers under it.
- plan
- The plan: what is in the working tree, how it divides into commits, and the fingerprint that says the plan is still about this tree.
- policy
- What this repository holds a commit to, as data.
- scopes
- The scope vocabulary, derived from the repository rather than declared beside it.
- verdict
- The judge: one commit message, against the policy, with every finding it earns.