Skip to content

HTTP API: worktree

Where every linked git worktree of this repository belongs and where each one is. The container is the primary checkout's sibling named with `-wt`, the path under it is the branch name with its hierarchy kept, and both are derived from git's own identity — the common directory, the registered worktrees, the branches — never from a registry, a configuration or the current directory. A worktree somewhere else is a typed diagnostic with a remedy; the migration that repairs it is a command-line operation of the same service.

Rendered from docs/generated/openapi.json, which the executable generates from its capability registry and CI refuses stale. The raw document is at /openapi.json; a running server serves the same document at its own /openapi.json and Swagger UI at /swagger.

worktree

Where every linked git worktree of this repository belongs and where each one is. The container is the primary checkout's sibling named with `-wt`, the path under it is the branch name with its hierarchy kept, and both are derived from git's own identity — the common directory, the registered worktrees, the branches — never from a registry, a configuration or the current directory. A worktree somewhere else is a typed diagnostic with a remedy; the migration that repairs it is a command-line operation of the same service.

GET /api/v1/worktrees worktree.topology

The whole topology

The repository, the container, the trunk and how it was decided, every registered worktree with its standing (primary, canonical, misplaced, detached, missing), its uncommitted work, its upstream distance and the issue its branch provably names, every local branch with or without a worktree and whether it is eligible for cleanup, every diagnostic with its code and remedy, and the tallies. Read from git on every call: the topology changes outside this process.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_worktrees
  • MCP resource {"name": "worktrees", "uri": "majordomus://worktrees"}
  • $ worktree topology

No parameters.

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/worktrees"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 RepositoryTopology The whole topology: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
GET /api/v1/worktrees/inspect worktree.inspect

One branch: where its worktree belongs and what is there

The canonical path of a branch, derived from its name alone, whether the branch exists, whether something occupies that path, the worktree holding the branch when one does, and what stands in the way of creating or migrating it. The answer for a branch that does not exist yet is the path `worktree create` would use.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_worktree_inspect
  • $ worktree inspect

Query parameters

nametyperequireddescriptionexamples
branch string yes The branch, full name (`feature/improve-cli`). It need not exist: the canonical path derives from the name alone. feature-branch: "feature/example"

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/worktrees/inspect?branch=feature%2Fexample"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 InspectReport One branch: where its worktree belongs and what is there: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
GET /api/v1/worktrees/migration worktree.migration_plan

What it would take to bring every worktree home

One step per misplaced worktree with a branch: where it is, where it belongs, how it would move, the uncommitted work that moves with it, and what blocks it; plus the exceptions the migration cannot address by design — detached worktrees, stale registrations, the primary checkout off the trunk — each with what a person does about it. Planning changes nothing; `majordomus worktree migrate` applies it with a fingerprint taken before and after every move.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_worktree_migration_plan
  • $ worktree migrate

No parameters.

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/worktrees/migration"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 MigrationPlan What it would take to bring every worktree home: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
GET /api/v1/worktrees/status worktree.status

Where this is, and whether that is where it belongs

One worktree — the repository's own, or the one holding the directory the caller names — with its standing, its branch, its canonical path, its uncommitted work counted, whether it is where it belongs, and how many errors the whole topology carries. What an agent reads before it starts, and what the guard decides on.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_worktree_status
  • $ worktree status

Query parameters

nametyperequireddescriptionexamples
path string no A directory inside one of this repository's worktrees; the answer is about that worktree. Relative to the primary checkout when relative (`.` is the primary checkout itself). Default: the repository the server was started for. A path in another repository is refused: the topology answers only about its own. primary-checkout: "."

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/worktrees/status?path=."

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 StatusReport Where this is, and whether that is where it belongs: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.
GET /api/v1/worktrees/uncommitted worktree.uncommitted

Uncommitted work in the linked worktrees, and what holds it

Every linked worktree whose index or working tree holds changes no commit carries: how many paths (staged, unstaged, untracked, conflicted), when the oldest change was made (the earliest modification time among the changed paths, the index file's standing in for a deleted one), and what holds the work — an active task record, an open episode that is not stranded, or a process whose working directory is inside. Work nothing holds and older than the threshold is `abandoned`, with the command that would put it on a remote `rescue/` ref; it is suggested, never run. The threshold is the policy's `session.stranded_after` unless `older_than` names another. Reads every worktree and writes none: every status runs without optional locks, so no index is refreshed.

  • query
  • behaviorally_verified
  • benchmark required
  • cache disabled
  • MCP tool majordomus_worktree_uncommitted
  • $ worktree uncommitted

Query parameters

nametyperequireddescriptionexamples
older_than string no The age past which work nothing holds is abandoned, as a person writes it (`90s`, `30m`, `12h`, `1d`). Default: the policy's `session.stranded_after`, which is also how long an open episode may be silent and still hold the work. one-hour: "1h"

Try it

The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.

curl -s "http://127.0.0.1:8741/api/v1/worktrees/uncommitted?older_than=1h"

Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.

Responses

statusbodymeaning
200 UncommittedReport Uncommitted work in the linked worktrees, and what holds it: the result.
400 ErrorBody invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects.
404 ErrorBody not_found: The input names something the repository does not hold.
500 ErrorBody internal: The capability failed for a reason of its own; the message names it.
default ErrorBody Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object.

Schemas

The components only this tag's operations use. Each row is the type's own field, its doc comment the description; a type another page owns links there.

BranchState

object
One local branch, with or without a work tree.
propertytyperequireddescription
name string yes The branch, short.
head string yes The commit it points at.
trunk boolean yes This branch is the trunk.
expected_path string | null no Where its work tree belongs.
worktree string | null no Where it is checked out, when it is.
upstream UpstreamState | null → UpstreamState no The upstream and its distance.
merged_into_trunk boolean | null no Reachable from the trunk. Absent when the trunk is unknown.
cleanup_eligible boolean yes Merged into the trunk, not the trunk, and either not checked out or checked out in a work tree known to be clean. Derived state, never acted on automatically.
issue string | null no The issue this branch provably names.

Used by RepositoryTopology

ContainerView

object
The container, as the topology reports it.
propertytyperequireddescription
path string yes The derived path.
suffix string yes The suffix it was derived with.
exists boolean yes It exists on disk. Created by the first worktree that needs it.

Used by RepositoryTopology StatusReport

DiagnosticCode

"worktree.path_mismatch" | "worktree.container_occupied" | "worktree.nested" | "worktree.destination_conflict" | "worktree.missing" | "worktree.stale_registration" | "worktree.branch_already_checked_out" | "worktree.detached" | "worktree.ephemeral" | "worktree.primary_on_non_trunk" | "worktree.trunk_in_linked_worktree" | "worktree.path_escape" | "worktree.invalid_branch_name" | "worktree.locked" | "worktree.migration_verification_failed" | "worktree.trunk_unknown" | "worktree.case_collision" | "worktree.cross_device"
The stable machine name of everything that can be wrong with the topology. One code per condition, reused by the command line, the API, MCP, the Cockpit, the tests and the documentation; the prose beside a code is rendered from the typed state.
  • worktree.path_mismatchA linked work tree is not at its branch's canonical path.
  • worktree.container_occupiedA linked work tree occupies the container path itself.
  • worktree.nestedA linked work tree sits inside the primary checkout or inside another work tree.
  • worktree.destination_conflictThe canonical path of a branch is occupied by something that is not its work tree.
  • worktree.missingA registered work tree's directory does not exist.
  • worktree.stale_registrationGit reports the registration as prunable.
  • worktree.branch_already_checked_outA branch is checked out somewhere other than its canonical path.
  • worktree.detachedA linked work tree has no branch.
  • worktree.ephemeralA linked work tree is a session's scratch checkout: under the temporary directory or under the primary checkout's .claude/worktrees/.
  • worktree.primary_on_non_trunkThe primary checkout holds a branch that is not the trunk.
  • worktree.trunk_in_linked_worktreeThe trunk is checked out in a linked work tree rather than the primary checkout.
  • worktree.path_escapeA derived path would leave the container. Cannot happen for a valid branch name.
  • worktree.invalid_branch_nameA branch name git accepted that this executable cannot derive a path for.
  • worktree.lockedA work tree is locked, so it cannot be moved until it is unlocked.
  • worktree.migration_verification_failedA move happened and the fingerprint after it differs from the one before.
  • worktree.trunk_unknownThe trunk could not be determined.
  • worktree.case_collisionTwo branch names map to one directory on a case-insensitive filesystem.
  • worktree.cross_deviceA move crossed devices and was made by copy, repair and verification.

Used by TopologyDiagnostic

DirtyState

object
Uncommitted work in a work tree, counted from git status --porcelain. Untracked content counts: it is exactly what a careless move loses.
propertytyperequireddescription
staged integer (uint) yes Entries with a change in the index.
unstaged integer (uint) yes Entries with a change in the work tree that is not in the index.
untracked integer (uint) yes Untracked, not ignored, files.
conflicted integer (uint) yes Entries with an unresolved merge conflict.
clean boolean yes Nothing above is non-zero.
in_progress string | null no A merge, rebase, cherry-pick, revert or bisect is in progress here.

Used by MigrationStep UncommittedWorktree WorktreeState

EnvrcApproval

one of 6 variants
What became of the .envrc of a worktree that was just created, found, or moved.
variant direnv accepted it: the next cd loads the environment.
outcome"approved"yes
variant The worktree has no .envrc; there is nothing to approve and nothing is blocked.
outcome"no_envrc"yes
variant direnv is not on the PATH; nothing was done, and nothing is blocked either, because nothing would load the file.
outcome"direnv_absent"yes
variant The worktree's .envrc is not the primary checkout's, so the approval given there does not carry. direnv allow in the worktree, after reading it, is the person's.
outcome"differs"yes
variant The primary checkout's .envrc is not approved, so there is no approval to carry.
outcome"not_approved_in_primary"yes
variant direnv was asked and refused, or could not be run; the message is its own.
messagestringyesdirenv's standard error, or the error running it.
outcome"failed"yes

Used by MigrationStep

HooksPath

one of 6 variants
What became of the hooks directory of a worktree that was just created, found or moved.

It travels in the JSON of worktree create, ensure, migrate and repair, tagged by outcome, so a reader branches on one word rather than parsing the line:

use majordomus_cli::worktree::HooksPath;

let set = HooksPath::Set {
    path: "/r-wt/fix/x/.githooks".into(),
    enabled: vec![],
};
let json = serde_json::to_value(&set).unwrap();
assert_eq!(json["outcome"], "set");
assert_eq!(json["path"], "/r-wt/fix/x/.githooks");
let back: HooksPath = serde_json::from_value(json).unwrap();
assert_eq!(back, set);
variant The repository's configuration sets no core.hooksPath: git's own hooks directory in the common directory (or a value of the user's) serves every worktree, and there is no tracked directory to follow.
outcome"not_configured"yes
variant Git already resolves this worktree's hooks to its own directory; nothing was set.
pathstringyesThe directory git runs the hooks from.
outcome"own"yes
variant This worktree's core.hooksPath was set, per worktree, to its own directory.
pathstringyesThe directory it now runs the hooks from.
enabledarray of stringyesRepository settings switched on so that a per-worktree value is honoured, as key=value; empty when they were on already.
outcome"set"yes
variant The hooks directory is outside the primary checkout — one directory shared on purpose — and every worktree runs it; nothing was set.
pathstringyesThe directory every worktree runs the hooks from.
outcome"shared"yes
variant The primary checkout's hooks directory has no counterpart in this worktree's tree, so the worktree keeps running the primary's: pointing it at a directory that does not exist would switch its hooks off.
missingstringyesThe directory that would be the worktree's own, and is not there.
runsstringyesThe directory the worktree runs the hooks from instead.
outcome"untracked"yes
variant Git was asked and refused, or could not be run; the message is its own.
messagestringyesWhat went wrong.
outcome"failed"yes

Used by MigrationStep

InspectReport

object
One branch, inspected: where its work tree belongs and what is there.
propertytyperequireddescription
branch string yes The branch asked about.
branch_exists boolean yes The branch exists locally.
expected_path string yes Where its work tree belongs.
destination_exists boolean yes Something exists at that path.
worktree WorktreeState | null → WorktreeState no The work tree that holds the branch, when one does.
canonical boolean yes The branch is checked out at its canonical path.
diagnostics array of TopologyDiagnostic → TopologyDiagnostic yes What stands in the way, if anything.

Used by worktree.inspect

MigrationAction

"move" | "move_via_staging" | "copy_and_repair" | "none"
How a step moves its worktree.
  • moveOne git worktree move.
  • move_via_stagingThe worktree is the container itself: out to a staging path, then into the container.
  • copy_and_repairThe move crossed devices: copy, git worktree repair, verify, remove the original.
  • noneNothing: the step is blocked.

Used by MigrationStep

MigrationPlan

object
A migration, planned or applied.
propertytyperequireddescription
schema string yes SCHEMA.
container string yes The container everything moves into.
steps array of MigrationStep → MigrationStep yes One step per misplaced worktree with a branch, container occupants first.
exceptions array of TopologyDiagnostic → TopologyDiagnostic yes Worktrees the migration cannot address by design: detached, missing, the primary checkout off the trunk. Each says what a person does about it.
movable integer (uint) yes Steps that can be carried out as things stand.
blocked integer (uint) yes Steps that cannot.
moved integer (uint) yes Steps carried out and verified.
failed integer (uint) yes Steps attempted and not verified.
applied boolean yes Whether anything was changed.
moved_current string | null no The new path of the worktree the command was run from, when that one moved.

Used by worktree.migration_plan

MigrationStep

object
One worktree to bring home.
propertytyperequireddescription
branch string yes The branch it holds.
from string yes Where it is.
to string yes Where it belongs.
head string | null no The commit checked out.
action MigrationAction → MigrationAction yes How it moves.
dirty DirtyState → DirtyState yes Its uncommitted work, which moves with it.
blockers array of TopologyDiagnostic → TopologyDiagnostic yes Why it cannot move, when it cannot.
outcome StepOutcome → StepOutcome yes What happened.
message string | null no What happened, in words, when it was not simply moved.
before WorktreeFingerprint | null → WorktreeFingerprint no The fingerprint before the move.
after WorktreeFingerprint | null → WorktreeFingerprint no The fingerprint after it.
differences array of string yes What differs between the two; empty when verified.
envrc EnvrcApproval | null → EnvrcApproval no What became of its .envrc under direnv once it was at its new path: the primary checkout's approval carried there, or why it was not. Only on a moved step.
hooks HooksPath | null → HooksPath no Which hooks it runs at its new path: a per-worktree core.hooksPath names the old one, so a moved worktree is bound again to its own tree's (I2718). Only on a moved step.

Used by MigrationPlan

OpenEpisode

object
An open episode in a work tree, as far as holding its work goes: it holds the work only while stranded is Some(false); a stranded or unjudgeable one holds nothing.

use majordomus_cli::worktree::OpenEpisode;
let unjudged = OpenEpisode {
    id: "s-20260101000000-0a0a".into(),
    last_sign_of_life: None,
    stranded: None,
};
let wire = serde_json::to_value(&unjudged).unwrap();
assert!(wire.get("stranded").is_none(), "what could not be judged is absent, not false");
let back: OpenEpisode = serde_json::from_value(wire).unwrap();
assert_eq!(back, unjudged);
propertytyperequireddescription
id string yes The episode.
last_sign_of_life string | null no Its newest sign of life, RFC 3339 in UTC.
stranded boolean | null no Silent longer than the threshold. Absent when that cannot be judged — no threshold, or no readable sign of life — and then it holds nothing.

Used by UncommittedWorktree

RepositoryTopology

object
The whole topology: what worktree list, the MCP resource, the HTTP route and the Cockpit all read.
propertytyperequireddescription
schema string yes SCHEMA.
repository RepositoryView → RepositoryView yes The repository.
container ContainerView → ContainerView yes The container every linked work tree belongs under.
trunk TrunkView → TrunkView yes The trunk.
observed_from string yes The work tree the call was answered from.
worktrees array of WorktreeState → WorktreeState yes Every registered work tree, primary first.
branches array of BranchState → BranchState yes Every local branch, by name.
diagnostics array of TopologyDiagnostic → TopologyDiagnostic yes Everything wrong with the topology, work trees first, then repository-wide.
tallies TopologyTallies → TopologyTallies yes The counts.
valid boolean yes No error-level diagnostic stands.

Used by worktree.topology

RepositoryView

object
The repository's identity, as the topology reports it.
propertytyperequireddescription
primary_worktree string yes The primary checkout: what the container is named after.
git_common_dir string yes The common git directory: the repository's identity.
name string yes The primary checkout's directory name.

Used by RepositoryTopology StatusReport

Standing

"primary" | "canonical" | "misplaced" | "detached" | "ephemeral" | "missing"
Where a work tree stands against the topology.
  • primaryThe primary checkout: exempt from the path rule, held to the trunk rule.
  • canonicalA linked work tree at exactly its branch's canonical path.
  • misplacedA linked work tree somewhere else. Migration brings it home.
  • detachedA linked work tree with no branch. It has no canonical path and is never moved.
  • ephemeralA linked work tree under the operating system's temporary directory or under the primary checkout's .claude/worktrees/: a session's scratch checkout, owned and removed by the harness that made it. Reported; migrated only on request.
  • missingA registration whose directory is gone. Repair drops it.

Used by WorktreeState

StatusReport

object
The answer to "where am I, and is that where I belong".
propertytyperequireddescription
schema string yes SCHEMA.
repository RepositoryView → RepositoryView yes The repository.
container ContainerView → ContainerView yes The container.
trunk TrunkView → TrunkView yes The trunk.
worktree WorktreeState → WorktreeState yes The work tree the call came from, with its uncommitted work counted.
canonical boolean yes Whether this work tree is where it belongs: canonical, or the primary checkout on the trunk, or detached.
repository_errors integer (uint) yes How many error-level diagnostics the whole topology carries.

Used by worktree.status

StepOutcome

"planned" | "blocked" | "moved" | "failed"
What became of a step.
  • plannedPlanned and not applied.
  • blockedNot carried out, with the blockers on the step.
  • movedMoved, and the fingerprint after equals the one before.
  • failedAttempted and not completed, or completed and not verified; the message says which.

Used by MigrationStep

Threshold

object
The age past which uncommitted work nothing holds is abandoned, and where it came from.

It is reported with every reading, so a reader can see what the verdicts were judged against; a threshold nothing declares is carried as absent, not filled in.

use majordomus_cli::worktree::Threshold;
let declared = Threshold::resolve(Some("12h"), None).unwrap();
let wire = serde_json::to_value(&declared).unwrap();
assert_eq!(wire["declared"], "12h");
assert_eq!(wire["seconds"], 43_200);
let absent: Threshold =
    serde_json::from_value(serde_json::json!({"source": "absent"})).unwrap();
assert_eq!((absent.declared, absent.seconds), (None, None), "absent is not a default");
propertytyperequireddescription
declared string | null no The value as written (12h). Absent when nothing declares one.
seconds integer | null no The same value, in seconds.
source string yes THRESHOLD_KEY, older_than when the caller named one, or absent.

Used by UncommittedReport

TopologyDiagnostic

object
One thing wrong with, or worth knowing about, the topology.
propertytyperequireddescription
code DiagnosticCode → DiagnosticCode yes The stable code.
severity Severity → Severity yes How serious it is.
path string | null no The work tree involved.
branch string | null no The branch involved.
expected string | null no Where the work tree belongs.
message string yes What is wrong, in one line.
remedy string yes The command that addresses it.

Used by InspectReport MigrationPlan MigrationStep RepositoryTopology WorktreeState

TopologyTallies

object
How many work trees and branches are in each state.
propertytyperequireddescription
worktrees integer (uint) yes Every registered work tree, primary included.
canonical integer (uint) yes Linked work trees at their canonical path.
misplaced integer (uint) yes Linked work trees somewhere else.
detached integer (uint) yes Linked work trees without a branch.
ephemeral integer (uint) yes Scratch checkouts of a session.
missing integer (uint) yes Registrations whose directory is gone.
locked integer (uint) yes Work trees git reports as locked.
dirty integer (uint) yes Work trees with uncommitted work, when it was asked for.
branches integer (uint) yes Local branches.
branches_without_worktree integer (uint) yes Local branches with no work tree.
cleanup_eligible integer (uint) yes Branches eligible for cleanup.
errors integer (uint) yes Error-level diagnostics.
warnings integer (uint) yes Warning-level diagnostics.

Used by RepositoryTopology

TrunkSource

"remote_head" | "default_branch_config" | "conventional_name" | "primary_checkout" | "unknown"
Where the trunk was learned from, in the order it is looked for.
  • remote_headrefs/remotes/<remote>/HEAD: what the remote calls its default branch.
  • default_branch_configinit.defaultBranch in the git configuration, and that branch exists locally.
  • conventional_nameExactly one of main and master exists locally.
  • primary_checkoutThe branch the primary checkout holds, because nothing else said.
  • unknownNothing said; the trunk is unknown and every check that needs it says so.

Used by TrunkView

TrunkView

object
The trunk, as the topology reports it.
propertytyperequireddescription
branch string | null no The branch, when known.
source TrunkSource → TrunkSource yes How it was decided.
checked_out_at string | null no Where it is checked out.

Used by RepositoryTopology StatusReport

UncommittedReport

object
Every linked work tree holding uncommitted work, with what holds it: the answer of worktree.uncommitted on every surface, and what doctor renders.

use majordomus_cli::worktree::uncommitted::UNCOMMITTED_SCHEMA;
use majordomus_cli::worktree::{Threshold, UncommittedReport, WorktreeService};
let dir = tempfile::tempdir().unwrap();
let init = std::process::Command::new("git")
    .args(["init", "-q"])
    .current_dir(dir.path())
    .status()
    .unwrap();
assert!(init.success());
// a repository with no linked work tree, and a policy that declares no threshold
let threshold = Threshold::resolve(None, None).unwrap();
let report: UncommittedReport =
    WorktreeService::open(dir.path()).unwrap().uncommitted(&threshold).unwrap();
assert_eq!(report.schema, UNCOMMITTED_SCHEMA);
assert!(report.worktrees.is_empty());
assert_eq!(report.tallies.measured, 0);
assert_eq!(report.findings.len(), 1, "a missing threshold is said, not assumed");
propertytyperequireddescription
schema string yes UNCOMMITTED_SCHEMA.
observed_at string yes When the reading was taken, RFC 3339 in UTC.
threshold Threshold → Threshold yes The threshold it was judged against.
worktrees array of UncommittedWorktree → UncommittedWorktree yes Every linked work tree with uncommitted work, abandoned first, then oldest first.
tallies UncommittedTallies → UncommittedTallies yes The counts.
findings array of string yes What could not be read or judged, in words.

Used by worktree.uncommitted

UncommittedTallies

object
The counts of an UncommittedReport: how many linked work trees were read, how many hold uncommitted work, and how those split into held, recent and abandoned. The three standings always add up to uncommitted; doctor reads abandoned to decide whether it has anything to warn about.

use majordomus_cli::worktree::UncommittedTallies;
let wire = serde_json::json!(
    {"measured": 4, "uncommitted": 3, "held": 1, "recent": 1, "abandoned": 1}
);
let t: UncommittedTallies = serde_json::from_value(wire.clone()).unwrap();
assert_eq!(t.held + t.recent + t.abandoned, t.uncommitted);
assert_eq!(serde_json::to_value(&t).unwrap(), wire, "the shape doctor's jq reads");
assert_eq!(UncommittedTallies::default().abandoned, 0, "nothing read, nothing abandoned");
propertytyperequireddescription
measured integer (uint) yes Linked work trees whose status was read.
uncommitted integer (uint) yes Of those, the ones holding uncommitted work: every entry of the listing.
held integer (uint) yes Held by a task, an episode or a process.
recent integer (uint) yes Held by nothing, younger than the threshold.
abandoned integer (uint) yes Held by nothing, older than the threshold.

Used by UncommittedReport

UncommittedWorktree

object
One linked work tree holding uncommitted work: how much, how old, what holds it, the verdict, and the command that would preserve it. A listing is in canonical order — abandoned first, then recent, then held, the oldest first inside each.

use majordomus_cli::worktree::{DirtyState, UncommittedWorktree, WorkStanding};
let entry = |path: &str, standing: WorkStanding, age: u64| UncommittedWorktree {
    path: path.into(),
    branch: None,
    label: path.into(),
    work: DirtyState { staged: 1, ..DirtyState::default() },
    paths: 1,
    oldest_change: None,
    age_seconds: Some(age),
    age: None,
    task: None,
    episodes: Vec::new(),
    process_inside: None,
    held_by: Vec::new(),
    standing,
    remedy: String::new(),
};
let mut listing = vec![
    entry("/wt/held-old", WorkStanding::Held, 900_000),
    entry("/wt/young", WorkStanding::Abandoned, 50_000),
    entry("/wt/older", WorkStanding::Abandoned, 90_000),
];
majordomus_cli::order::canonical(&mut listing);
let paths: Vec<&str> = listing.iter().map(|w| w.path.as_str()).collect();
assert_eq!(paths, ["/wt/older", "/wt/young", "/wt/held-old"]);
propertytyperequireddescription
path string yes Absolute, as git holds it.
branch string | null no The branch checked out there. Absent when detached.
label string yes The branch, or detached/<short commit>.
work DirtyState → DirtyState yes The counts, as worktree status gives them.
paths integer (uint) yes Every changed path: staged, unstaged, untracked and conflicted together.
oldest_change string | null no When the oldest change was made: the earliest modification time among the changed paths, the index file's standing in for a path that no longer exists. RFC 3339.
age_seconds integer | null no Seconds since then.
age string | null no The same, for a person (3d, 5h, 40m).
task string | null no The active task recorded there.
episodes array of OpenEpisode → OpenEpisode yes The episodes open there.
process_inside boolean | null no A process has its working directory inside it. Absent when that could not be read.
held_by array of string yes What holds it, in words; empty when nothing does.
standing WorkStanding → WorkStanding yes The verdict.
remedy string yes The command that would put this work on a remote ref. Suggested, never run.

Used by UncommittedReport

UpstreamState

object
A branch's upstream and how far the two have moved apart, from for-each-ref in one subprocess for every branch, never a fetch.
propertytyperequireddescription
name string yes The upstream ref, short (origin/master).
ahead integer | null no Commits here that the upstream lacks.
behind integer | null no Commits upstream that this branch lacks.
gone boolean yes The upstream ref no longer exists.

Used by BranchState WorktreeState

WorkStanding

"held" | "recent" | "abandoned"
What a work tree's uncommitted work is, judged: held by something, recent, or abandoned. judge is the one place it is decided, and doctor warns only on abandoned.

use majordomus_cli::worktree::uncommitted::{judge, WorkStanding};
let standing = judge(false, Some(90_000), Some(43_200));
assert_eq!(standing, WorkStanding::Abandoned);
assert_eq!(serde_json::to_value(standing).unwrap(), "abandoned");
  • heldA task, an episode or a process holds it.
  • recentNothing holds it, and it is younger than the threshold.
  • abandonedNothing holds it, and it is older than the threshold — or its age cannot be read.

Used by UncommittedWorktree

WorktreeFingerprint

object
What a work tree held at one moment, reduced to digests.
propertytyperequireddescription
branch string | null no The branch, or none when detached.
head string | null no The commit.
index_digest string yes A digest of the index: every tracked path with its mode, blob and stage.
staged_diff_digest string yes A digest of the staged diff against HEAD.
unstaged_diff_digest string yes A digest of the unstaged diff against the index.
untracked_manifest_digest string yes A digest of every untracked file: path, kind, size and content.
untracked_files integer (uint) yes How many untracked files the manifest holds.
ignored_manifest_digest string yes A digest of every ignored entry: path, kind and size, never content.
in_progress string | null no The operation in progress, if any.
tree_manifest_digest string | null no A digest of every entry of the directory tree, ignored included: path, kind, size and link target. Taken only for a move made by copying, where the rename guarantee does not hold.

Used by MigrationStep

WorktreeKind

"primary" | "linked"
Whether a work tree is the repository's own checkout or one linked to it.
  • primaryThe main work tree. It hosts the trunk, is never moved and is never removed.
  • linkedA linked work tree. Belongs at its branch's canonical path.

Used by WorktreeState

WorktreeState

object
One work tree, as the topology sees it.
propertytyperequireddescription
path string yes Absolute, as git holds it.
kind WorktreeKind → WorktreeKind yes Primary or linked.
standing Standing → Standing yes Where it stands against the topology.
branch string | null no The branch checked out here, short. Absent when detached.
label string yes The name it is shown under: the branch, or detached/<short commit>.
head string | null no The commit checked out here.
detached boolean yes HEAD is detached here.
expected_path string | null no Where this branch's work tree belongs. Absent when detached, or for the primary checkout on the trunk.
exists boolean yes The directory git registered still exists on disk.
current boolean yes The call came from inside this work tree.
locked string | null no Locked, with git's reason; an empty string when it recorded none.
prunable string | null no Prunable, with git's reason.
dirty DirtyState | null → DirtyState no Uncommitted work here. Absent when it was not asked for: it costs one subprocess per work tree, and a topology check does not need it.
upstream UpstreamState | null → UpstreamState no The branch's upstream and its distance from it.
issue string | null no The issue this branch provably names: a path component of the branch equal to an issue id of .ai/repo/project/issues/, or beginning with it and a hyphen. Nothing is inferred from similarity.
diagnostics array of TopologyDiagnostic → TopologyDiagnostic yes What is wrong with this work tree, if anything.

Used by InspectReport RepositoryTopology StatusReport

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