Skip to content
Cockpit0.29.0
A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live

commit.plan

What the working tree would commit

What it is

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.

Descriptor

Canonical id
commit.plan
Module
commit
Kind
query
Stability
behaviorallyverified
Provenance
declared
Source
apps/majordomus-cli/src/capability/builtin/commit.rs
Cache
disabled
Benchmark
required
Tags
commitgitplan

Run

query
A query. It reads and changes nothing.

This capability takes no input.

GET /api/v1/commit/plan

Projections

InterfaceAsWhere
MCP toolmajordomus_commit_plan-
MCP resourcemajordomus://commit/plan-
HTTPGET /api/v1/commit/planSwagger
Command linemajordomus commit plan-

Schemas

Input · Empty
{
  "type": "object",
  "additionalProperties": false,
  "description": "No input.",
  "title": "Empty"
}
Output · CommitPlan
{
  "type": "object",
  "properties": {
    "fingerprint": {
      "$ref": "#/$defs/PlanFingerprint",
      "description": "What the plan was derived from, and what makes it stale."
    },
    "tree": {
      "$ref": "#/$defs/WorkingTreeState",
      "description": "The tree as it stood."
    },
    "groups": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/CommitGroup"
      },
      "description": "The commits proposed, in the order they should be made."
    },
    "diagnostics": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/Diagnostic"
      },
      "description": "What a person should know before acting on it."
    }
  },
  "required": [
    "fingerprint",
    "tree",
    "groups",
    "diagnostics"
  ],
  "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```",
  "title": "CommitPlan",
  "$defs": {
    "PlanFingerprint": {
      "type": "object",
      "properties": {
        "repository": {
          "type": "string",
          "description": "The repository: git's common directory, which every worktree of one repository\nshares and no two repositories do."
        },
        "worktree": {
          "type": "string",
          "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."
        },
        "head": {
          "type": "string",
          "description": "HEAD when the plan was derived, or `unborn`."
        },
        "changes": {
          "type": "string",
          "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."
        }
      },
      "required": [
        "repository",
        "worktree",
        "head",
        "changes"
      ],
      "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```"
    },
    "WorkingTreeState": {
      "type": "object",
      "properties": {
        "branch": {
          "type": [
            "string",
            "null"
          ],
          "description": "The branch, or `None` when HEAD is detached."
        },
        "head": {
          "type": [
            "string",
            "null"
          ],
          "description": "HEAD's full name, or `None` in a repository with no commits."
        },
        "upstream": {
          "type": [
            "string",
            "null"
          ],
          "description": "The upstream ref, when the branch has one."
        },
        "ahead": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "Commits this branch has that its upstream does not."
        },
        "behind": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "Commits the upstream has that this branch does not."
        },
        "in_progress": {
          "type": [
            "string",
            "null"
          ],
          "description": "A merge, rebase, cherry-pick or bisect in progress, when there is one."
        },
        "changes": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/PathChange"
          },
          "description": "Every change, ordered by stage then path."
        }
      },
      "required": [
        "ahead",
        "behind",
        "changes"
      ],
      "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```"
    },
    "PathChange": {
      "type": "object",
      "properties": {
        "path": {
          "type": "string",
          "description": "Repository-relative, forward slashes."
        },
        "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": {
          "type": "string",
          "description": "The two-letter status git reported, verbatim."
        },
        "partial": {
          "type": "boolean",
          "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."
        }
      },
      "required": [
        "path",
        "stage",
        "status"
      ],
      "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```"
    },
    "ChangeStage": {
      "oneOf": [
        {
          "type": "string",
          "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."
        }
      ],
      "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```"
    },
    "CommitGroup": {
      "type": "object",
      "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": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The paths, in order."
        },
        "rationale": {
          "type": "string",
          "description": "Why these paths are one commit, in evidence a reader can check."
        },
        "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"
      ],
      "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```"
    },
    "CommitMessage": {
      "type": "object",
      "properties": {
        "header": {
          "$ref": "#/$defs/CommitHeader",
          "description": "The first line, parsed."
        },
        "body": {
          "type": "string",
          "description": "Everything between the header and the trailer block, with surrounding blank lines\nremoved. Empty when the message is a subject alone."
        },
        "trailers": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/CommitTrailer"
          },
          "description": "The `Key: value` lines of the final paragraph, in the order they were written."
        }
      },
      "required": [
        "header",
        "body",
        "trailers"
      ],
      "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```"
    },
    "CommitHeader": {
      "type": "object",
      "properties": {
        "word": {
          "type": "string",
          "description": "The type as written: `feat`, `fix`, `wip`, or the empty string when the subject\ncarries no `type: ` head at all."
        },
        "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": {
          "type": [
            "string",
            "null"
          ],
          "description": "The parenthesised scope, when there is one."
        },
        "breaking": {
          "type": "boolean",
          "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`."
        },
        "subject": {
          "type": "string",
          "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."
        }
      },
      "required": [
        "word",
        "kind",
        "breaking",
        "subject"
      ],
      "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```"
    },
    "ReleaseChangeKind": {
      "oneOf": [
        {
          "type": "string",
          "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."
        }
      ],
      "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."
    },
    "CommitTrailer": {
      "type": "object",
      "properties": {
        "key": {
          "type": "string",
          "description": "The key, as written: `Refs`, `Co-Authored-By`, `BREAKING CHANGE`."
        },
        "value": {
          "type": "string",
          "description": "Everything after the colon and one space."
        }
      },
      "required": [
        "key",
        "value"
      ],
      "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```"
    },
    "ScopeSuggestion": {
      "type": "object",
      "properties": {
        "scope": {
          "type": "string",
          "description": "The scope."
        },
        "commits": {
          "type": "integer",
          "format": "uint",
          "minimum": 0,
          "description": "How many prior commits associate this scope with directories these paths are in."
        },
        "directory": {
          "type": "string",
          "description": "The directory prefix the association was strongest at."
        },
        "ambiguous": {
          "type": "boolean",
          "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."
        }
      },
      "required": [
        "scope",
        "commits",
        "directory",
        "ambiguous"
      ],
      "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```"
    },
    "Diagnostic": {
      "type": "object",
      "properties": {
        "severity": {
          "$ref": "#/$defs/Severity",
          "description": "How bad."
        },
        "code": {
          "type": "string",
          "description": "A stable machine-readable code, e.g. `unknown_key`, `duplicate_identity`."
        },
        "path": {
          "type": [
            "string",
            "null"
          ],
          "description": "Repository-relative path of the file concerned, when there is one."
        },
        "message": {
          "type": "string",
          "description": "What is wrong, for a person; the code is for a program."
        }
      },
      "required": [
        "severity",
        "code",
        "message"
      ],
      "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."
    },
    "Severity": {
      "oneOf": [
        {
          "type": "string",
          "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."
        }
      ],
      "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."
    }
  }
}

Examples

These are the capability's own benchmark cases: the same inputs the benchmark runs and the OpenAPI document shows.

default
{}