Skip to content

commit.plan

The working tree as git reports it — branch, upstream, divergence, every staged, unstaged and untracked path, and any merge or rebase in progress — divided into the commits the history's own scoping supports, each with the evidence for it, under a fingerprint of the repository, the worktree, HEAD and the exact change set, so that a plan acted on later can be refused rather than applied to a tree it was not made for.

The working tree as git reports it — branch, upstream, divergence, every staged, unstaged and untracked path, and any merge or rebase in progress — divided into the commits the history's own scoping supports, each with the evidence for it, under a fingerprint of the repository, the worktree, HEAD and the exact change set, so that a plan acted on later can be refused rather than applied to a tree it was not made for.

Exposure

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

surfaceas
MCPtool majordomus_commit_plan
resource majordomus://commit/plan
HTTPGET /api/v1/commit/plan operationId commit.plan
command linemajordomus commit plan

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 · Empty

No input.

No properties: the capability takes no input.

JSON Schema
{
  "additionalProperties": false,
  "description": "No input.",
  "title": "Empty",
  "type": "object"
}

output · CommitPlan

A plan over a working tree.

use majordomus_cli::commit::{CommitPlan, PlanFingerprint, WorkingTreeState};
let plan = CommitPlan {
    fingerprint: PlanFingerprint::of("/r/.git", "/r", &WorkingTreeState::default()),
    tree: WorkingTreeState::default(),
    groups: Vec::new(),
    diagnostics: Vec::new(),
};
assert!(plan.groups.is_empty(), "a clean tree proposes nothing");
propertytyperequireddescription
diagnostics array yes What a person should know before acting on it.
fingerprint reference yes What the plan was derived from, and what makes it stale.
groups array yes The commits proposed, in the order they should be made.
tree reference yes The tree as it stood.
JSON Schema
{
  "$defs": {
    "ChangeStage": {
      "description": "Where one path stands between the index and the working tree.\n```\nuse majordomus_cli::commit::ChangeStage;\n// ordered so that what would be in the next commit sorts first\nassert!(ChangeStage::Staged < ChangeStage::Unstaged);\nassert!(ChangeStage::Unstaged < ChangeStage::Untracked);\n```",
      "oneOf": [
        {
          "const": "staged",
          "description": "Different in the index from HEAD: it would be in the next commit.",
          "type": "string"
        },
        {
          "const": "unstaged",
          "description": "Different in the working tree from the index: it would not.",
          "type": "string"
        },
        {
          "const": "untracked",
          "description": "Not tracked at all.",
          "type": "string"
        }
      ]
    },
    "CommitGroup": {
      "description": "One commit a plan proposes.\nOne commit a plan proposes: its message, its paths, and why those paths are one commit.\n\n```\nuse majordomus_cli::commit::{plan::group, CommitGroup, ScopeVocabulary};\nlet mut v = ScopeVocabulary::default();\nv.learn(\"commit\", \"src/commit\", 40);\nlet g: Vec<CommitGroup> = group(&[\"src/commit/a.rs\".into()], &v, &[]);\n// the subject is empty on purpose: what a change did is the one thing no evidence in\n// the tree can state\nassert_eq!(g[0].message.header.subject, \"\");\nassert_eq!(g[0].message.header.scope.as_deref(), Some(\"commit\"));\nassert!(g[0].rationale.contains(\"40 prior commit\"), \"{}\", g[0].rationale);\n```",
      "properties": {
        "message": {
          "$ref": "#/$defs/CommitMessage",
          "description": "The message as proposed. The summary is the one field a person is expected to\nreplace: nothing here can know *why* a change was made, and a planner that wrote a\nconfident sentence about it would be writing fiction into the history."
        },
        "paths": {
          "description": "The paths, in order.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "rationale": {
          "description": "Why these paths are one commit, in evidence a reader can check.",
          "type": "string"
        },
        "scope_from": {
          "anyOf": [
            {
              "$ref": "#/$defs/ScopeSuggestion"
            },
            {
              "type": "null"
            }
          ],
          "description": "The scope suggestion and what it was derived from, when there was one."
        }
      },
      "required": [
        "message",
        "paths",
        "rationale"
      ],
      "type": "object"
    },
    "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"
    },
    "PathChange": {
      "description": "One path, and where it stands.\n```\nuse majordomus_cli::commit::ChangeStage;\nuse majordomus_cli::commit::plan::PathChange;\n// staged and modified again since: the commit would carry the staged half only\nlet c = PathChange {\n    path: \"src/a.rs\".into(), stage: ChangeStage::Staged, status: \"MM\".into(), partial: true,\n};\nassert!(c.partial);\n```",
      "properties": {
        "partial": {
          "description": "Set when the path is staged *and* modified again since: committing it commits the\nstaged half only, which is a thing a person should be told rather than discover.",
          "type": "boolean"
        },
        "path": {
          "description": "Repository-relative, forward slashes.",
          "type": "string"
        },
        "stage": {
          "$ref": "#/$defs/ChangeStage",
          "description": "Whether it is staged, unstaged or untracked. A path modified both in the index and\nin the working tree appears once, as `ChangeStage::Staged`, with `partial` set."
        },
        "status": {
          "description": "The two-letter status git reported, verbatim.",
          "type": "string"
        }
      },
      "required": [
        "path",
        "stage",
        "status"
      ],
      "type": "object"
    },
    "PlanFingerprint": {
      "description": "What a plan was derived from.\n\nCompared, never trusted: a plan whose fingerprint differs from the tree in front of it\ndescribes a tree that no longer exists.\n```\nuse majordomus_cli::commit::{PlanFingerprint, WorkingTreeState};\nlet f = PlanFingerprint::of(\"/repo/.git\", \"/repo\", &WorkingTreeState::default());\nassert_eq!(f.head, \"unborn\", \"a repository with no commits says so\");\n```",
      "properties": {
        "changes": {
          "description": "SHA-256 over every change, its stage and its status, in order. Restaging one file\nchanges it; touching a file the plan does not contain does not.",
          "type": "string"
        },
        "head": {
          "description": "HEAD when the plan was derived, or `unborn`.",
          "type": "string"
        },
        "repository": {
          "description": "The repository: git's common directory, which every worktree of one repository\nshares and no two repositories do.",
          "type": "string"
        },
        "worktree": {
          "description": "The worktree: this checkout's own path. Two worktrees of one repository have the\nsame repository and different worktrees, which is what keeps their plans apart.",
          "type": "string"
        }
      },
      "required": [
        "repository",
        "worktree",
        "head",
        "changes"
      ],
      "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"
        }
      ]
    },
    "ScopeSuggestion": {
      "description": "Which scope a set of paths belongs to, and the evidence for it.\n```\nuse majordomus_cli::commit::{ScopeSuggestion, ScopeVocabulary};\nlet mut v = ScopeVocabulary::default();\n// two scopes with equal claim to one directory: reported, never broken silently\nv.learn(\"a\", \"src\", 7);\nv.learn(\"b\", \"src\", 7);\nlet s: ScopeSuggestion = v.suggest(&[\"src/x.rs\".into()]).expect(\"a tie\");\nassert!(s.ambiguous);\n```",
      "properties": {
        "ambiguous": {
          "description": "Whether another scope was as strongly associated. A tie is reported rather than\nbroken silently: a planner that guessed would be inventing the one field of a commit\nmessage that a reader uses to navigate.",
          "type": "boolean"
        },
        "commits": {
          "description": "How many prior commits associate this scope with directories these paths are in.",
          "format": "uint",
          "minimum": 0,
          "type": "integer"
        },
        "directory": {
          "description": "The directory prefix the association was strongest at.",
          "type": "string"
        },
        "scope": {
          "description": "The scope.",
          "type": "string"
        }
      },
      "required": [
        "scope",
        "commits",
        "directory",
        "ambiguous"
      ],
      "type": "object"
    },
    "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"
        }
      ]
    },
    "WorkingTreeState": {
      "description": "What git says about the working tree right now.\n```\nuse majordomus_cli::commit::WorkingTreeState;\nlet clean = WorkingTreeState::default();\nassert!(clean.is_clean());\nassert!(clean.staged().is_empty());\n```",
      "properties": {
        "ahead": {
          "description": "Commits this branch has that its upstream does not.",
          "format": "uint",
          "minimum": 0,
          "type": "integer"
        },
        "behind": {
          "description": "Commits the upstream has that this branch does not.",
          "format": "uint",
          "minimum": 0,
          "type": "integer"
        },
        "branch": {
          "description": "The branch, or `None` when HEAD is detached.",
          "type": [
            "string",
            "null"
          ]
        },
        "changes": {
          "description": "Every change, ordered by stage then path.",
          "items": {
            "$ref": "#/$defs/PathChange"
          },
          "type": "array"
        },
        "head": {
          "description": "HEAD's full name, or `None` in a repository with no commits.",
          "type": [
            "string",
            "null"
          ]
        },
        "in_progress": {
          "description": "A merge, rebase, cherry-pick or bisect in progress, when there is one.",
          "type": [
            "string",
            "null"
          ]
        },
        "upstream": {
          "description": "The upstream ref, when the branch has one.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "ahead",
        "behind",
        "changes"
      ],
      "type": "object"
    }
  },
  "description": "A plan over a working tree.\n```\nuse majordomus_cli::commit::{CommitPlan, PlanFingerprint, WorkingTreeState};\nlet plan = CommitPlan {\n    fingerprint: PlanFingerprint::of(\"/r/.git\", \"/r\", &WorkingTreeState::default()),\n    tree: WorkingTreeState::default(),\n    groups: Vec::new(),\n    diagnostics: Vec::new(),\n};\nassert!(plan.groups.is_empty(), \"a clean tree proposes nothing\");\n```",
  "properties": {
    "diagnostics": {
      "description": "What a person should know before acting on it.",
      "items": {
        "$ref": "#/$defs/Diagnostic"
      },
      "type": "array"
    },
    "fingerprint": {
      "$ref": "#/$defs/PlanFingerprint",
      "description": "What the plan was derived from, and what makes it stale."
    },
    "groups": {
      "description": "The commits proposed, in the order they should be made.",
      "items": {
        "$ref": "#/$defs/CommitGroup"
      },
      "type": "array"
    },
    "tree": {
      "$ref": "#/$defs/WorkingTreeState",
      "description": "The tree as it stood."
    }
  },
  "required": [
    "fingerprint",
    "tree",
    "groups",
    "diagnostics"
  ],
  "title": "CommitPlan",
  "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
directcovered1default
mcpcovered1default
httpcovered1default

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