Skip to content

commit.validate

One message against this repository's commit policy: the grammar, the subject width, whether the scope is one the history uses, whether every record the message names exists, whether a fix carries a test, and whether a breaking change explains itself — as typed findings with severities, or an exemption for a subject git composed. The same verdict the commit-msg hook refuses with and the gate reads the history through.

One message against this repository's commit policy: the grammar, the subject width, whether the scope is one the history uses, whether every record the message names exists, whether a fix carries a test, and whether a breaking change explains itself — as typed findings with severities, or an exemption for a subject git composed. The same verdict the commit-msg hook refuses with and the gate reads the history through.

Exposure

Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.

surfaceas
MCPtool majordomus_commit_validate
HTTPGET /api/v1/commit/validate operationId commit.validate
command linemajordomus commit validate

Schemas

The canonical input and output, from the Rust types; the MCP tool schema and the OpenAPI parameters and responses are derived from these.

input · ValidateInput

A message to judge, and what is known about the commit it would make.

use majordomus_cli::capability::builtin::commit::ValidateInput;
// paths are optional: a caller that does not know them gets the judgements that need
// none, rather than a guess
let v: ValidateInput = serde_json::from_str(r#"{"message": "feat(a): a thing"}"#).unwrap();
assert!(v.paths.is_none());
propertytyperequireddescription
message string yes The commit message, as it would be stored. Comment lines are ignored, as git ignores them, so what is judged is what would be committed.
paths array | null no The paths the commit would contain. Omit them and the judgements that need them — whether a fix carries a test — are not made rather than guessed. default null
JSON Schema
{
  "additionalProperties": false,
  "description": "A message to judge, and what is known about the commit it would make.\n```\nuse majordomus_cli::capability::builtin::commit::ValidateInput;\n// paths are optional: a caller that does not know them gets the judgements that need\n// none, rather than a guess\nlet v: ValidateInput = serde_json::from_str(r#\"{\"message\": \"feat(a): a thing\"}\"#).unwrap();\nassert!(v.paths.is_none());\n```",
  "properties": {
    "message": {
      "description": "The commit message, as it would be stored. Comment lines are ignored, as git ignores\nthem, so what is judged is what would be committed.",
      "type": "string"
    },
    "paths": {
      "default": null,
      "description": "The paths the commit would contain. Omit them and the judgements that need them —\nwhether a fix carries a test — are not made rather than guessed.",
      "items": {
        "type": "string"
      },
      "type": [
        "array",
        "null"
      ]
    }
  },
  "required": [
    "message"
  ],
  "title": "ValidateInput",
  "type": "object"
}

output · CommitVerdict

What the judge concluded: the message as it was parsed, whether anything of error severity was found, the exemption when git composed the subject rather than a person, and every finding in CommitRule::ALL order — which is the order the evidence for them appears in the message, so a person reads them in the order they would fix them.

use majordomus_cli::commit::{judge, CommitPolicy, CommitSubject, CommitVerdict};
let v: CommitVerdict = judge(&CommitSubject::of("update stuff"), &CommitPolicy::default());
assert!(!v.passed);
assert_eq!(v.findings[0].code, "commit.not_conventional");
assert_eq!(v.message.header.subject, "update stuff", "what was judged, not what was typed");
propertytyperequireddescription
exempt one of 2 no Why the commit was not judged, when it was not.
findings array yes Every finding, in CommitRule::ALL order.
message reference yes The message as parsed; what was judged, not what was typed.
passed boolean yes Whether nothing of error severity was found.
JSON Schema
{
  "$defs": {
    "CommitExemption": {
      "description": "Why a commit was not judged.\n```\nuse majordomus_cli::commit::{judge, CommitExemption, CommitPolicy, CommitSubject};\nlet v = judge(&CommitSubject::of(\"Merge pull request #1 from a/b\"), &CommitPolicy::default());\nassert_eq!(v.exempt, Some(CommitExemption::GitAuthored));\n```",
      "oneOf": [
        {
          "const": "git_authored",
          "description": "git composed the subject: a merge, a revert, a fixup.",
          "type": "string"
        }
      ]
    },
    "CommitHeader": {
      "description": "The header of a commit message: its first line, taken apart.\n\n`word` is the type as the author wrote it and `kind` is what that word names; they\ndiffer exactly when the word is not one of the eleven the convention defines, and\nkeeping both is what lets a reader see `wip(site): halfway through` as its author typed\nit while a judge still reports that `wip` is not a type.\n```\nuse majordomus_cli::commit::CommitHeader;\nuse majordomus_cli::release::ChangeKind;\nlet h = CommitHeader::parse(\"fix(plan): the fingerprint names what moved\");\nassert_eq!(h.word, \"fix\");\nassert_eq!(h.kind, ChangeKind::Fix);\nassert_eq!(h.scope.as_deref(), Some(\"plan\"));\nassert!(!h.breaking);\nassert_eq!(h.subject, \"the fingerprint names what moved\");\n```",
      "properties": {
        "breaking": {
          "description": "Whether the head carried the `!` that marks a breaking change. The `BREAKING CHANGE:`\ntrailer is a property of the body and is read by `crate::commit::CommitMessage`.",
          "type": "boolean"
        },
        "kind": {
          "$ref": "#/$defs/ReleaseChangeKind",
          "description": "What `Self::word` names; `ChangeKind::Other` for a word the convention does not\ndefine, and for a subject with no head."
        },
        "scope": {
          "description": "The parenthesised scope, when there is one.",
          "type": [
            "string",
            "null"
          ]
        },
        "subject": {
          "description": "What the commit says it did. For a header that does not parse this is the whole\nfirst line, unchanged, and for an unknown type word it keeps the word: nothing that\nthe author wrote is dropped on the way through.",
          "type": "string"
        },
        "word": {
          "description": "The type as written: `feat`, `fix`, `wip`, or the empty string when the subject\ncarries no `type: ` head at all.",
          "type": "string"
        }
      },
      "required": [
        "word",
        "kind",
        "breaking",
        "subject"
      ],
      "type": "object"
    },
    "CommitMessage": {
      "description": "A commit message taken apart: header, body, trailers.\n```\nuse majordomus_cli::commit::CommitMessage;\nlet m = CommitMessage::parse(\"feat(a): a thing\\n\\nThe body.\\n\\nRefs: I1305\\n\");\nassert_eq!(m.header.subject, \"a thing\");\nassert_eq!(m.body, \"The body.\");\nassert_eq!(m.trailers.len(), 1);\n```",
      "properties": {
        "body": {
          "description": "Everything between the header and the trailer block, with surrounding blank lines\nremoved. Empty when the message is a subject alone.",
          "type": "string"
        },
        "header": {
          "$ref": "#/$defs/CommitHeader",
          "description": "The first line, parsed."
        },
        "trailers": {
          "description": "The `Key: value` lines of the final paragraph, in the order they were written.",
          "items": {
            "$ref": "#/$defs/CommitTrailer"
          },
          "type": "array"
        }
      },
      "required": [
        "header",
        "body",
        "trailers"
      ],
      "type": "object"
    },
    "CommitTrailer": {
      "description": "One `Key: value` line in the trailer block at the end of a commit message.\n```\nuse majordomus_cli::commit::CommitMessage;\nuse majordomus_cli::commit::CommitTrailer;\nlet m = CommitMessage::parse(\"fix(x): a thing\\n\\nWhy.\\n\\nRefs: I1305\\n\");\nlet t: &CommitTrailer = &m.trailers[0];\nassert_eq!(t.key, \"Refs\");\nassert_eq!(t.value, \"I1305\");\n```",
      "properties": {
        "key": {
          "description": "The key, as written: `Refs`, `Co-Authored-By`, `BREAKING CHANGE`.",
          "type": "string"
        },
        "value": {
          "description": "Everything after the colon and one space.",
          "type": "string"
        }
      },
      "required": [
        "key",
        "value"
      ],
      "type": "object"
    },
    "Diagnostic": {
      "description": "One finding about the declarative state, named by a stable code, tied to a path where\nthere is one, and carrying the command that reproduces it where there is one.",
      "properties": {
        "code": {
          "description": "A stable machine-readable code, e.g. `unknown_key`, `duplicate_identity`.",
          "type": "string"
        },
        "message": {
          "description": "What is wrong, for a person; the code is for a program.",
          "type": "string"
        },
        "path": {
          "description": "Repository-relative path of the file concerned, when there is one.",
          "type": [
            "string",
            "null"
          ]
        },
        "severity": {
          "$ref": "#/$defs/Severity",
          "description": "How bad."
        }
      },
      "required": [
        "severity",
        "code",
        "message"
      ],
      "type": "object"
    },
    "ReleaseChangeKind": {
      "description": "What a conventional commit says it did.\n\nThe set is the one the repository's own commit convention uses; a commit whose subject\ndoes not parse is `ChangeKind::Other` and still appears, because a changelog that\nsilently drops what it cannot classify is a changelog that lies by omission.",
      "oneOf": [
        {
          "const": "feat",
          "description": "A capability a person did not have before.",
          "type": "string"
        },
        {
          "const": "fix",
          "description": "Behaviour that was wrong and is not any more.",
          "type": "string"
        },
        {
          "const": "perf",
          "description": "Performance, with the behaviour unchanged.",
          "type": "string"
        },
        {
          "const": "refactor",
          "description": "Structure, with the behaviour unchanged.",
          "type": "string"
        },
        {
          "const": "docs",
          "description": "Documentation.",
          "type": "string"
        },
        {
          "const": "test",
          "description": "Tests.",
          "type": "string"
        },
        {
          "const": "chore",
          "description": "The build, the pipeline, the tooling.",
          "type": "string"
        },
        {
          "const": "ci",
          "description": "Continuous integration.",
          "type": "string"
        },
        {
          "const": "style",
          "description": "Formatting, with the behaviour unchanged.",
          "type": "string"
        },
        {
          "const": "build",
          "description": "The build system or its dependencies.",
          "type": "string"
        },
        {
          "const": "revert",
          "description": "A change that undoes another.",
          "type": "string"
        },
        {
          "const": "merge",
          "description": "An integration: one line of work brought into another.\n\nNot in the conventional-commit specification, and in this repository's history 38\ntimes and counting, because integration here is a deliberate act with a subject\nsomebody writes — `merge: bring origin/master into feature/x` — and not the sentence\n`git merge` composes. Declaring it is what lets the validator hold those subjects to\nthe same width and the same references as every other commit, instead of either\nrefusing a convention this project practises or exempting it from being checked at\nall. The changelog never renders it: `commits::in_range` reads the log with\n`--no-merges`, so a merge commit is not a release entry however its subject is\nspelled.",
          "type": "string"
        },
        {
          "const": "other",
          "description": "A commit whose subject does not parse as a conventional commit.",
          "type": "string"
        }
      ]
    },
    "Severity": {
      "description": "How bad a diagnostic is. `Error` excludes the file it concerns from the index and puts\nthe index into the degraded state; `Warning` and `Info` do neither.",
      "oneOf": [
        {
          "const": "info",
          "description": "Worth knowing; changes nothing.",
          "type": "string"
        },
        {
          "const": "warning",
          "description": "Something to look at; the object is still served.",
          "type": "string"
        },
        {
          "const": "error",
          "description": "The file is excluded and the index is degraded.",
          "type": "string"
        }
      ]
    }
  },
  "description": "What the judge concluded: the message as it was parsed, whether anything of error\nseverity was found, the exemption when git composed the subject rather than a person, and\nevery finding in `CommitRule::ALL` order — which is the order the evidence for them\nappears in the message, so a person reads them in the order they would fix them.\n\n```\nuse majordomus_cli::commit::{judge, CommitPolicy, CommitSubject, CommitVerdict};\nlet v: CommitVerdict = judge(&CommitSubject::of(\"update stuff\"), &CommitPolicy::default());\nassert!(!v.passed);\nassert_eq!(v.findings[0].code, \"commit.not_conventional\");\nassert_eq!(v.message.header.subject, \"update stuff\", \"what was judged, not what was typed\");\n```",
  "properties": {
    "exempt": {
      "anyOf": [
        {
          "$ref": "#/$defs/CommitExemption"
        },
        {
          "type": "null"
        }
      ],
      "description": "Why the commit was not judged, when it was not."
    },
    "findings": {
      "description": "Every finding, in `CommitRule::ALL` order.",
      "items": {
        "$ref": "#/$defs/Diagnostic"
      },
      "type": "array"
    },
    "message": {
      "$ref": "#/$defs/CommitMessage",
      "description": "The message as parsed; what was judged, not what was typed."
    },
    "passed": {
      "description": "Whether nothing of error severity was found.",
      "type": "boolean"
    }
  },
  "required": [
    "message",
    "passed",
    "findings"
  ],
  "title": "CommitVerdict",
  "type": "object"
}

Policies

benchmark
required — a target on every transport the exposure declares; the cases are the input type's
cache
disabled — every call runs the handler

Benchmark targets

Derived from the registry for this repository: one requirement per transport, and the cases the input type provides. The whole matrix is on the benchmarks page.

transportstatecasestargets
directcovered3conventional
not-conventional
merge
mcpcovered3conventional
not-conventional
merge
httpcovered3conventional
not-conventional
merge

Searches the titles and descriptions of every page this site publishes.