Expand description
What: Internal native cli-git implementation, not yet the production executable. Why: The Rust rewrite preserves Git’s own argument, repository and transaction semantics.
In TS you’d write (pseudocode):
// Internal modules used by the forthcoming native entry point and consumer verification.Modules§
- action
- The decision one invocation ends in: become Git, or print and exit. What: The decision one wrapper invocation ends in. Why: Forwarding replaces the process and cannot run inside a unit test; returning the decision as data lets every path be checked directly.
- announced_
query - The four top-level spellings after which the wrapper announces itself,
and the text it prints once Git has answered.
What:
Recognize the four top-level spellings after which the wrapper says it is not plain Git
(
--version,-v,--help,-h), and build the text it prints once Git has answered. Why: A caller who typesgit --versionshould be able to tell that a policy wrapper sits in front of Git, without the wrapper changing a single byte of what Git itself printed. Every other query (--exec-path,--html-path, …) stays an exact process replacement. - auto_
push - Post-commit auto-push of a landed commit: the push argv, the surfaced output and the failure reporting.
What:
Post-commit auto-push of a landed commit: which push runs, what its output shows,
and how a coordination failure is reported without failing the commit.
Why:
Every commit in this repository is backed up (
AGENTS.mdrule APG), and the incumbent’ssrc/auto-push.tsdecides the push argv from the upstream state, prints only GitHubremote:lines on success and the full output on failure, and keeps the commit’s exit status when the push fails. The landed commit is an input, so the integrator can call this after any landing. - auto_
push_ branch_ key - The per-branch push coordination file name: a reversible, flat, case-safe key.
What:
The reversible, flat, case-insensitive-safe file name of one branch’s push coordination files.
Why:
The per-branch push lock and
last-pushedrecord live under<git-common-dir>/cli-git/push/, and the incumbent (src/auto-push-branch-key.ts) and this wrapper must name them identically, because both wrappers contend on the same lock while both are installed. Uppercase letters are escaped too, so refs that differ only in case never share a file on a case-insensitive filesystem. - auto_
push_ dirty_ paths - The paths of a dirty worktree and of a fast-forward, and their overlap.
What:
The paths a dirty worktree has uncommitted work in, the paths a fast-forward would change, and the overlap
between them.
Why:
After an auto-push publishes a merge, local
maincan follow it even in a worktree that holds uncommitted work, provided the move changes none of the files that work touches. Another session’s staged renames then never keep this branch behind the remote. - auto_
push_ reconcile - Publishing a landed commit that a moved remote branch rejected: the merge is made from objects alone and pushed.
What:
Publishing a landed commit when the remote branch advanced after the commit landed:
the merge of the remote tip and the local tip is computed in the object store,
committed without a worktree or an index, and pushed with a plain push.
Why:
A rejected
pushof a branch that the remote moved cannot succeed again, and an ordinary merge in the active worktree can be blocked, or made unsafe, by another consumer’s uncommitted files.git merge-tree --write-treeandgit commit-treeread and write only objects, so the active worktree, its index and its untracked files are never touched while merging, and both histories stay reachable from the published tip. Only after the push succeeds is the branch fast-forwarded, and only where nothing can be lost: a dirty worktree follows too when the move changes none of the files its uncommitted work touches. An amended or rebased branch is merged like any other: nothing already published is lost, and the replaced commits stay in the remote history beside the new ones. - auto_
push_ record - The per-branch
last-pushedrecord: validation, tolerant reads and atomic publication. What: The per-branchlast-pushedrecord that single-flight auto-push coordinates through. Why: The incumbent (src/auto-push-record.ts) and this wrapper must read each other’s records, because a commit waiting on a push made by the other wrapper decides from the same file. The record is a cache: an absent or unreadable record only costs an extra push, so only a read failure other than “absent” stops the caller. - auto_
push_ single_ flight - Single-flight push coordination per branch: owner lock, record joins and the push itself.
What:
Single-flight auto-push per branch: one owner lock serializes the pushes of a branch,
and the
last-pushedrecord names the most recent attempt. Why: A commit that a recorded successful push already covers finishes without pushing, and a commit that waits behind a live pusher joins the push it waited for, so concurrent landings on one branch cost fewer pushes than commits. The incumbent’ssrc/auto-push-single-flight.tsdecides these joins, and the lock and record paths must match its paths byte for byte. - blob_
batch - Loading many blobs through one
git cat-file --batch. What: Loading many blobs through onegit cat-file --batch, optionally from a private object store, with strict parsing of the batch output. Why: Post-landing completion reads the original and intended bytes of every added path; one batch replaces a Git start per object (src/policy-engine/blob-batch.ts). - bounded_
child - One child program run with a time bound and output caps, through files instead of pipes. What: Run one child program to completion with a time bound and output caps: its input comes from a file, its two outputs go to files, and the parent polls it. Why: The Markdown policy starts the linter once per candidate, and a child that hangs, floods its output or exits early must never hang or exhaust the wrapper. Files instead of pipes need no reader or writer threads: a child that stops reading its input cannot block the parent, a child that writes more than the cap is stopped when the parent sees the file grow past it, and a grandchild that keeps an output open cannot delay the parent once the child itself has exited. Every file is an anonymous temporary file that the operating system removes when it is closed.
- bounded_
push - The auto-push’s real
git pushwith a deadline, its low-speed abort and the lock wait limit derived from it. What: Run the auto-push’s realgit pushwith a deadline, and derive the wait limits the push coordination uses. Why: A push whose connection stalls would otherwise hold the branch’s push lock forever, and every later commit on that branch would wait behind it. The push is stopped when its deadline passes, the commit stays saved locally, and the failure is recorded like any other failed push. - bounded_
query - Read-only Git queries of the auto-push, each with a deadline and separate output streams. What: Run one read-only Git query with a deadline and separate output streams. Why: The auto-push asks Git a handful of questions before it pushes, and a question that never returns would hold the commit and the branch’s push lock for good. A query that outlives its deadline is stopped and reported, so the commit stays saved.
- candidate_
attribute - Which candidate paths have their
textGit attribute unset, read from the state the candidates were listed from. What: Which candidate paths have theirtextGit attribute unset (-text, which thebinarymacro includes), resolved bygit check-attragainst the same state the candidates were listed from. Why: A path whosetextattribute is unset declares that its exact bytes matter, as a fuzz seed’s trailing bytes do. The attribute lives in the package’s own.gitattributes, so it moves with the package, unlike a path list kept elsewhere. Staged and projected candidates are answered from the private index, so a.gitattributesstaged by the same command applies to it and an unstaged worktree edit does not; landed and pushed candidates are answered from the tree of the commit they came from. - candidate_
batch - Pure framing of one
git cat-file --batchreply. What: Read onegit cat-file --batchreply from a byte stream, with no process involved. Why: Object bytes are untrusted input that may contain anything, including text that looks like a reply header. The reply is therefore framed only by the header’s declared size, never by searching content, and every malformed shape is a typed failure instead of a guess. - candidate_
error - The one failure type of the candidate layer and its closed list of causes. What: The one failure type of the candidate layer, with a closed list of causes. Why: Every candidate failure stops policy evaluation (a file that could not be read was not checked), and callers branch on the cause without parsing message text.
- candidate_
known_ content - Which candidates hold content that a parent commit’s tree already holds, found by exact object name. What: Which candidates hold content that a parent commit’s tree already holds, found by exact blob object name. Why: A pure rename or a copy re-lists a file as an addition, and a merge lists every file one parent lacks as an addition, although the bytes are already in history. Scanning them again refuses moves of files that were accepted when they entered history, while an object name is the hash of the bytes, so a candidate whose object a parent holds carries no byte that is new. The comparison is by object name only: never by pathname, never by similarity, so one changed byte makes the content new.
- candidate_
object - Validated Git object names and the file modes a candidate can carry. What: The two Git vocabulary types every candidate carries: an object name and a file mode. Why: Object names are written into the object reader’s request stream, so only text proven to be a complete hexadecimal name may ever become one; modes decide whether a candidate has blob content at all.
- candidate_
prediction - What
git addwould stage, and the worktree files a direct command selects. What: The two candidate sources that need a private index: whatgit addwould stage, and the worktree files a directgit cli-git checkorfixselects. Why: Both are answered before Git touches the real index.git addis replayed on a private copy of the index, and the candidates are the entries the replay changed, as the installed wrapper’sadd-policy-facts.tsandadd-staged-delta.tsdefine them. A direct command stages its scope withgit add --allon a private copy, asdirect-check-facts.tsdoes, and every selected entry is a candidate. Either way the bytes are blobs Git hashed from the worktree at that moment, read through the one object reader, so the number of Git processes does not grow with the number of files. - candidate_
private_ index - A private,
timestamp-preserving copy of the real index,
removed when dropped.
What:
A private copy of the real index,
in its own directory beside it,
removed when
the copy is dropped.
Why:
Predicting what
git addwould stage means running it against an index nobody else reads, so the real index never changes before the policies have answered. The copy keeps the real index’s access and modification times: Git re-reads an entry whose stat data is not older than the index file, and a copy with a fresh time would make it trust stat data from the same second as a later edit of the same size, so the prediction could miss that edit (the installed wrapper’sindex-file-timestamps.ts, issue #544). - candidate_
reader - The long-lived object reader that answers every blob read of an invocation.
What:
One long-lived
git cat-file --batchprocess that answers every object read of an invocation. Why: Starting Git costs far more than reading one object, so candidate bytes are read through a single process whose count does not grow with the number of files. Blob bytes are remembered by object name: a name always denotes the same bytes, so a remembered blob can never become stale. - candidate_
record - Pure parsing of raw NUL-delimited changed-path records.
What:
Parse the NUL-delimited raw records of
git diff-indexandgit diff-tree. Why: One listing process names every changed path with its mode, object and change kind, so no per-path Git process is ever needed. Pathnames are kept as the raw bytes Git printed; they are never decoded. - candidate_
stage - Pure parsing of
git ls-files --stagerecords and the delta between two index states. What: Parse the NUL-delimited stage records ofgit ls-files --stage -z, find the paths one staging operation changed, and turn them into candidate records. Why:git addis checked before Git runs it, by replaying it on a private copy of the index. The paths it would stage are exactly those whose index records differ between the copy before and after the replay. Comparing complete records, conflict stages included, finds a resolved conflict like any other change, and an unrelated conflict elsewhere in the index never stops the comparison. Pathnames stay the raw bytes Git printed. - candidate_
store - Per-invocation listing,
lazy bytes and invalidation of candidate versions.
What:
The per-invocation owner of candidate versions and candidate bytes.
Why:
Policies read committed or staged content,
never whatever is in the live
worktree.
A version costs one listing process,
its bytes come from one
long-lived object reader,
and neither count grows with the number of files.
After a fix or replay changed the index,
invalidateretires every version. - candidate_
tree - The files of a commit’s tree with their modes and objects.
What: Parse the NUL-delimited records of
git ls-tree -r -z --full-tree <commit>: every file of one commit with its mode and object. Why: The dependent-version policy compares the candidate state withHEAD: it reads each workspace manifest’sHEADbytes, and a direct fix may change a tracked file it did not select only whenHEADholds that file as the same ordinary blob. One listing answers both for every path. Pathnames stay the raw bytes Git printed. - candidate_
version - Immutable candidate versions: identity, pathname, mode, change and object per path. What: An immutable candidate version: the paths one Git state changes, each with its identity, mode and object. Why: Every policy of one pass must judge the same files. A version is listed once and then only read, so path metadata is shared instead of re-queried, and a later fix or replay produces a new version instead of altering this one.
- capture_
invocation - The invocation capture:
repository layout, symbolic
HEAD, conclusion kind, ref backend and base. What: The transaction facts recorded before any Git mutation: the repository layout, the symbolicHEADtarget, the conclusion kind, the ref storage backend, and the preparation base. Why: Every later read ofHEADinside a transaction uses the recorded base or the landed commit, never liveHEAD, because another landing may have moved it since the invocation started. The layout query and the base read run as separate steps, so pruning sees the transaction before the base is read (src/policy-engine/commit-transaction-capture.ts,captureInvocationLayoutandcaptureInvocationBase). - capture_
order - The capture under capture order:
the capture lock, the store identity, the sequence and
captured.json. What: The capture under capture order: the per-worktree capture lock, the store identity, the sequence allocation and its durable write, the listing of the paths the capture read from the worktree, andcaptured.json. Why: Every commit transaction captures its content while holding the short per-worktree capture lock and takes the next sequence number under it, so sequence numbers give one total order of the captured disk states of a worktree. The incumbent and this wrapper share the store, so the lock, the identity and the write order must match exactly (src/policy-engine/commit-capture-order-store.ts,commit-capture-order-capture.ts). - capture_
records captured.json, landed-capture records, their recording and pruning. What:captured.json(a transaction’s position in its worktree’s capture order) and the landed-capture records, with recording after a landing and pruning. Why: Replay decides a path both a prepared commit and an earlier-landed commit captured from this worktree by capture order; a landed record is kept while any published transaction may replay over it and pruned after (src/policy-engine/commit-capture-order-journal.ts,commit-capture-order-records.ts,commit-capture-order-prune.ts).- capture_
store - The per-worktree capture store:
location,
sequence and identity.
What:
The per-worktree capture store:
its location,
its sequence file and its identity file,
read as the incumbent reads them.
Why:
Every commit transaction captures under a short per-worktree lock and takes the next
sequence number,
so sequence numbers order the captured disk states of one worktree.
Recovery and pruning read the store;
both wrappers share it
(
src/policy-engine/commit-capture-order-store.ts):<git-dir>/cli-git-captures/{capture.lock/, worktree-id, sequence, landed/<oid>.json}. - child_
class - The classes of Git step a commit starts for itself, with the bound and the notice grace of each.
What:
The two kinds of Git step a commit starts for itself, and the bound and the notice grace of each.
Why:
One bound for every step had to be long enough for the slowest, so a stalled
rev-parseheld a commit for as long as a stalledpack-objects, and the first notice came after one second for steps that only needed a moment. The classes follow what a step touches, measured on the repository this crate lives in (doc/planning/cli-git-commit-timeouts.md): a query reads refs, commits and trees and ends in milliseconds whatever the worktree holds, while a step that writes objects, or that compares the index with the worktree, costs time in proportion to the bytes it reads (reset,update-index --refreshandls-files --modifiedtook 2.9 s on a stat-dirty 20 000-file index against 0.03 s on a clean one, and a cold disk multiplies that). The user’s owngit commitwith its hooks is not a class: its runtime belongs to the user and it is never bounded. - child_
environment - Variables added to every real-Git child: lock PID injection and the forward-target marker. What: The environment variables cli-git adds for every real Git it starts. Why: Native Git must write lock-owner PID files, and a wrapper must be able to notice that another wrapper selected it as “real Git”.
- child_
watch - A watcher for waits that block on a child process: notices at the cadence and the child stopped at the bound.
What:
A watcher for a wait that blocks inside a read or a wait on a child process:
it prints a notice after the grace and at every interval while the wait lasts,
and stops the child when the wait crosses its bound.
Why:
The wrapper reads Git objects from a long-lived
git cat-file --batchand runs Git steps of a commit with their output captured. Both block in the operating system with nothing printed and no end, so a stalled child looks the same as a slow one and holds the commit for good (issue 1811). A watcher thread sees the wait from outside: the waiting thread marks where a wait begins and ends, the watcher prints, and at the bound it ends the child so the blocked call returns. - command_
add - The Git 2.56.0 option table of
git addand its bulk-staging facts. What: The completegit addoption table of Git 2.56.0 and the bulk-staging facts read from it. Why: The add-explicit policy rejects staging that names no explicit path. Git accepts-Ainside any cluster (-vA), by abbreviation (--al) and through--no-ignore-removal; a list of exact tokens misses those. - command_
branch_ create - Branch-creation facts of
git branch,git checkoutandgit switch. What: Branch-creation facts ofgit branch,git checkoutandgit switchregions, read as Git 2.56.0 reads them. Why: The branch-worktree policy rejects creating a branch in the current worktree. It needs the explicit forms and the name Git may turn into a new local branch by matching a remote branch, for every spelling Git accepts. Gotcha: The facts read option names and argument counts only. Where Git later refuses for an option value, a combination of options, configuration or repository state, the answer stays “creates”: it errs toward the policy seeing the command. - command_
branch_ table - The complete Git 2.56.0 option table of
git branch. What: The complete option table ofgit branchin Git 2.56.0. Why: Whethergit branch <name>creates a branch depends on which action options were given, and whether a token is a name depends on each option’s arity. Rows followoptions[]incmd_branch(builtin/branch.c:992-1050) in source order. - command_
checkout_ table - The complete Git 2.56.0 option tables of
git checkoutandgit switch. What: The complete option tables ofgit checkoutandgit switchin Git 2.56.0. Why: Both commands create branches explicitly (-b,-c,--orphan,--track) and implicitly (a name that matches one remote branch). The tables are assembled as Git assembles them: the command’s own rows, then the shared groups (builtin/checkout.c:1786-1847, 2118-2129, 2170-2179). - command_
clean - The Git 2.56.0 option table of
git cleanand its deletion facts. What: The completegit cleanoption table of Git 2.56.0 and the deletion facts read from it. Why: The linked-worktree policy guards a clean that can delete files. Git’s last--dry-run/--no-dry-runwins, in clusters (-ndX) and abbreviations (--dry). - command_
commit - Facts about the arguments after
git commit. What: Facts about the arguments aftergit commit, read as Git 2.56.0 reads them. Why: The commit-only transform, the dry-run check and the commit transaction all decide from these facts. Every fact is the final state after Git applied each option in order, so--all --no-allreportsalloff, exactly asgit commitwould act. - command_
commit_ table - The complete Git 2.56.0 option table of
git commit. What: The complete option table ofgit commitin Git 2.56.0. Why: Declaring every option Git accepts removes the guess about whether an undeclared option consumes the next token. Rows followbuiltin_commit_options(builtin/commit.c:1705-1785) in source order, which the completion control compares. - command_
config - The form and file scope of a
git configregion. What: The form and file scope of agit configregion, read as Git 2.56.0 reads it. Why: The require-root policy exemptsgit configwhen it works on the per-user or system file, or lists. Whether--globalis that option or a value being stored depends on the form:git config user.name --globalwrites the text--globalinto the repository’s own configuration. - command_
config_ table - The Git 2.56.0 option groups of
git config. What: The option groups ofgit configin Git 2.56.0, one constant per group. Why: Git builds eachgit configsubcommand’s table from shared groups (CONFIG_LOCATION_OPTIONS,CONFIG_TYPE_OPTIONS,CONFIG_DISPLAY_OPTIONS, builtin/config.c:67-117) plus a few rows of its own. Keeping the same groups keeps one copy of every row. - command_
discard - The commands that can overwrite or delete uncommitted state, read as Git reads them.
What:
Which regions of
git checkout,git switch,git restore,git rm,git mv,git checkout-index,git read-treeand the--abortforms ofmerge,rebase,cherry-pick,revertandamcan overwrite or delete uncommitted state. Why: The linked-worktree policy confines such commands: it rejects them in the main worktree and, once the worktree holds uncommitted state, in a linked worktree too. Each region is read as Git 2.56.0 reads it, so a spelling Git accepts as--forceis not missed and a value that looks like a flag is not mistaken for one. Gotcha: Where Git itself refuses the region the answer is the refusal; the caller lets Git say so. - command_
options - Git 2.56.0
parse-optionstokenization of one command’s argument region. What: Read one Git command’s arguments the way Git 2.56.0parse-options.creads them. Why: A policy that guesses which token is an option, a value or a path can be bypassed by a spelling Git accepts. Every per-command module declares Git’s own option table and this module walks the arguments exactly once with Git’s rules. - command_
options_ query - Final-state and position questions over a tokenized region. What: Small read-only questions over a tokenized Git argument region. Why: Git applies options in order and the last use wins, so command modules ask for final states and positions instead of counting spellings.
- command_
push - The Git 2.56.0 option table of
git pushand its facts. What: The completegit pushoption table of Git 2.56.0 and the facts read from it. Why: The atomic-push transform must know whether the caller already chose atomicity, and the manual-push gate whether the push is a dry run; both in every spelling Git accepts (--at,--no-at,-nf), which a list of exact tokens misses. - command_
reset - The Git 2.56.0 option table of
git resetand its mode. What: The completegit resetoption table of Git 2.56.0 and the mode read from it. Why: The linked-worktree policy guards resets that rewrite worktree files:--hard,--mergeand--keep. The five mode options write one variable, so only the last one counts, in any abbreviation Git accepts. - command_
stash git stashsubcommand dispatch and wrapper-flag positions. What: Whichgit stashsubcommand a region runs in Git 2.56.0, and where wrapper-only flags sit in it. Why: The linked-worktree policy guards stash; its escape hatch must be found in option position of the subcommand that actually reads the arguments, so a message or a path that spells the hatch is forwarded untouched.- command_
stash_ table - The Git 2.56.0 option tables of every
git stashsubcommand. What: The option tables of everygit stashsubcommand in Git 2.56.0. Why:git stashdispatches to subcommands that each parse their own options with their own flags (builtin/stash.c); whether a token is an option value depends on which subcommand reads it. - command_
status - The Git 2.56.0 option table of
git status, its facts and the advice-key reading. What: The completegit statusoption table of Git 2.56.0, the machine-readable fact read from it, and the reading ofadvice.statusHintsset before the subcommand. Why: The status-hints transform must not override an explicit caller choice, and the wrapper adds no note to output a program will parse. - command_
worktree - Whether a
git worktreeinvocation creates or moves a worktree. What: The one fact the wrapper needs aboutgit worktree: whether the invocation creates or moves a worktree. Why: Onlygit worktree addandgit worktree moveare sources of ignored-state synchronization, and the worktree-copy opt-out is written after that second word. - commit_
args - Argument transforms of native preparation: normalisation, global split,
-aremoval and private arguments. What: Argument transforms of native preparation: the normalisedcommitargument region, the split of global options from the commit region, the removal of-a/--all, and the arguments of the realgit committhat run against the complete private index. Why: Nativegit commitmust see the policy-settled index, not worktree bytes again, so selection controls and pathspecs are removed once the private index already holds them (src/policy-engine/commit-preparation-native.ts,commit-transaction-selection.ts,src/parser/commit-normalise.ts). The removals match the incumbent token for token, including its use oflastIndexOf, so a-mvalue equal to a pathspec is removed from the same position. - commit_
command - The
git commitcommand: the transaction, the settled events, the post-commit gate and the backup. What: Thegit commitcommand of the native wrapper: the transaction, the settled events, the post-commit gate and the automatic backup, in the incumbent’s order. Why: The incumbent (src/bin.ts) runs the commit transaction instead of forwarding the commit, prints the pre-forward events only after the transaction returned, then runs the post-commit lifecycle for a landed commit and, when the gate passes, the automatic push. A commit that landed nothing exits with the transaction’s status. - commit_
conclusion - The conclusion of a settled commit transaction: preparation, landing and post-landing completion.
What:
The conclusion of a settled commit transaction:
the normalization-only landing when the corrections removed every change, otherwise the hook dispatcher,
native preparation in the shadow, the prepared record, the landing loop and the post-landing completion.
Why:
The incumbent’s
commit-transaction-conclusion.tsruns these steps in this order: the tree is written from the stable private index, the dispatcher shim is shared by preparation and the post-landingpost-commit, the prepared record is durable before the preparation marker, and the transaction directory is removed only after the worktree copies are installed. - commit_
convergence - Slice 6: the commit path’s bounded policy convergence over the private index.
What:
The commit path’s bounded policy convergence over the private commit index:
each changed pass writes its corrections into the index,
then the whole ordered pass restarts until it proposes nothing.
Why:
The commit path must end with the corrections the policies settled on,
and must stop when they repeat a state or do not settle.
The loop is the shared
converge; this module supplies the commit path’s three operations, as the direct fix does for the worktree. Exact prior states are compared in memory; the incumbent also streams them to snapshot files, which only serve the same comparison. - commit_
facts - Rule facts from the real repository with candidates read from the private index.
What:
The repository facts of a commit transaction:
every rule fact is read from the real repository, and the candidate files are read from the private index
and the shadow object store against the recorded base.
Why:
The incumbent’s rules (
commit-only,commit-index,commit-sequencer) ask real Git about the real index and the real sequencer state, while the content policies read the private commit index that the transaction corrects. Pointing every fact at the private index would answer the rules about a state the user never staged. - commit_
index - The private commit index: copies, selection modes, pathspec files and listings.
What:
The private commit index of one transaction:
its timestamp-preserving copies of the real index,
its initialization for each selection mode,
the intended tree written from it,
its path listings,
the pathspec-file source, and
interactive and patch selection run against it.
Why:
Git’s staging and selection run against a private index and the shadow object store,
so the real index and object store change only at landing.
A copy keeps the real index’s timestamps, because Git re-reads a stat entry
whose time is not older than the index file (
index-file-timestamps.ts, issue #544). - commit_
landing_ lock - The landing lock a landing attempt takes, with the recovery every acquisition runs first.
What:
The per-worktree landing lock a landing attempt takes, and the recovery every acquisition runs first.
Why:
A crashed landing must be resolved before another landing moves the ref or the index, so the lock is acquired
and then dead landings are recovered under it (
src/policy-engine/commit-landing-lock.ts). An invocation nested under the holder, proven by an inherited landing lease, proceeds under its ancestor’s lock. - commit_
landing_ loop - The landing loop with replay, revalidation and the starvation reservation.
What:
The landing loop: land, and after each lost race replay and revalidate outside both locks, until the commit
lands, its replay conflicts, revalidation rejects it, or the target cannot take it.
Why:
Each replay rebuilds the prepared commit from the preparation base onto the target that won, so every replay
starts from the same prepared change. After
landing.reserveAfterLostRaceslost races the transaction takes the landing reservation before it replays, and every landing attempt yields to another transaction’s live reservation; the reservation is released when the loop ends (src/policy-engine/commit-landing-loop.ts). - commit_
lifecycle - Slice 9 skeleton: the order around a landed commit and a guarded Git child; not reachable until landing is merged.
What:
The call order around a guarded Git child:
install the signal guard, run the child, release the locks and the transaction, drop the guard,
then exit with the child’s code.
Why:
The incumbent releases the transaction before the signal guard goes away, so a signal sent during
release is still relayed to the child.
The order after a landed commit (automatic maintenance, then the post-commit hook) is not here:
the incumbent runs maintenance without stopping on failure (
src/policy-engine/commit-transaction-conclusion.ts), and the post-landing lane owns that sequence. Nothing calls this yet: the command path keeps refusinggit commituntil landing is merged. - commit_
no_ change - The normalization-only transaction for a correction that removed every change.
What:
The normalization-only transaction: a correction whose complete intended tree equals the recorded base lands as
a reconciliation of the index and the worktree copies, without an empty commit.
Why:
The incumbent’s
completeNoChangeTransactionlands the normalization through the landing critical section against the recorded base, so a base that moved since invocation fails without touching the index (src/policy-engine/commit-transaction-no-change.ts). - commit_
preparation - Native
git commitin the shadow, the prepared commit read back andprepared.json. What: Nativegit commitin the shadow repository against the private commit index, the prepared commit read back from the shadow, andprepared.json. Why: Git owns the hooks, the editor, the templates, the message cleanup and the signing; this module only points Git at the shadow and the private index. Every hook is off in the shadow (its config switches the commit events off and the hooks-off option comes first), so Git starts no external program for a hook and never starts the wrapper. A failing preparation removes the shadow so no shadow outlives the failure (src/policy-engine/commit-preparation-native.ts,src/shadow-repository/shadow-repository.ts). - commit_
recovery - Startup recovery of interrupted commit transactions.
What:
Startup recovery of interrupted commit transactions for the invocation’s worktree.
Why:
Before configuration loads,
and before read-only commands that already asked Git for the
repository location,
every dead owner’s transaction is recovered or fails closed;
live
owners are skipped.
The legacy single-journal directory is not recovered here:
the
command stops and says how to recover it with the previous executable (the owner’s
verdict,
doc/planning/cli-git-rust-open-decisions.md, “Legacy single-journal directory”). An empty registry costs one directory read and no Git process (src/policy-engine/commit-transaction-recovery.ts). - commit_
replay_ options - The replay-relevant commit options.
What:
The commit options a replay repeats outside native
git commit: the key a signed commit is re-signed with. Why: A replay rebuilds the prepared commit on a moved parent, so it must sign the same way (src/policy-engine/commit-replay-options.ts). - commit_
revalidation - Revalidation of a replayed tree outside both landing locks.
What:
Revalidation of a replayed tree, outside both landing locks:
a private index for the replayed tree, the shadow
HEADat the replay parent, the policies converged again over that index, andpre-commitre-run whenever the tree differs from the one it last approved. Why: A replay merges changes the policies and the hook never saw together, so the merged tree must pass the same gates as the prepared one (src/policy-engine/commit-revalidation.ts). - commit_
staged_ additions - The finding for a staged deletion that an explicit-path commit cannot record.
What:
The staged additions an explicit-path commit must carry into its private index, and the pathspec-file reader
that tells which selection they belong to.
Why:
A path staged as an addition in the real index is tracked there, so native
git commit -- <path>takes its worktree content without asking any ignore rule. The private index of an explicit-path commit starts from the recorded base, where such a path is untracked, sogit addapplies the ignore rules: a directory pathspec silently matches nothing, and a file pathspec fails with a hint to force-add thatcommitdoes not accept. Carrying the real index’s entry first makes the path tracked in the private index, andgit addthen refreshes it from the worktree exactly as native Git does. - commit_
staged_ deletions - What:
The commit-only finding for a staged deletion that an explicit-path commit cannot record because the path is
still on disk.
Why:
An explicit pathspec selects worktree content, never index state, so a deletion staged with
git rm --cachedfor a path that stays on disk (an ignored editor file, for example) is not the content the pathspec selects. Without this finding Git answers “nothing to commit”, or a privategit addfails with a hint to force-add the ignored path, which would re-track the file instead of recording its removal. - commit_
transaction - The commit transaction in the incumbent’s order, up to the settled policies.
What:
The commit transaction in the incumbent’s order, up to the settled policies:
layout capture, the published workspace, the preparation base,
preparing.json, the shadow repository, the private index under capture order, interactive selection, and the bounded policy convergence. Why: The incumbent (src/policy-engine/commit-transaction.ts) captures before it mutates, publishes the directory before the base is read, and takes the landing lock only inside the landing attempt, never through preparation: holding it here would serialize concurrent commits. The conclusion (commit_conclusion.rs) takes the settled state to a landed commit. - commit_
workspace - The disposable, recoverable workspace of one commit transaction.
What:
The disposable, recoverable workspace of one commit transaction:
a published transaction directory with its owner record, the shadow repository path, and disposal that leaves
everything in place once a landing record handed it to recovery.
Why:
The incumbent’s
createCommitTransactionWorkspace(src/policy-engine/commit-transaction-workspace.ts) publishes the directory before any Git mutation, so recovery can skip a live owner and retire a dead one, and disposal removes the shadow before the directory so no shadow outlives the journal that names it. - commit_
worktree_ records - The worktree completion records of selected corrections and of hook changes.
What:
The worktree completion records a landed commit owes the worktree: selected final-newline corrections.
Why:
The commit keeps what the private index holds, so the real worktree still holds the bytes the user wrote.
After the landing, each recorded file takes the committed bytes only while it still holds its earlier bytes
(
src/policy-engine/commit-transaction-selected-worktree.ts). - conclusion_
cleanup - Native conclusion cleanup reproduced in the owning worktree.
What:
Native conclusion cleanup reproduced in the owning worktree’s Git directory after a
merge,
cherry-pick or revert conclusion lands,
and the names of that state.
Why:
Native
git commitremovesMERGE_HEADand its siblings in the shadow; the real worktree must end the same way, but an entry is removed only while it still holds the bytes or value copied at preparation, because a changed entry belongs to a later command (src/shadow-repository/shadow-conclusion-cleanup.ts,-files.ts,-names.ts). - conclusion_
copy - The shadow conclusion copies at preparation.
What:
The merge, cherry-pick and revert conclusion state copied into the shadow at preparation,
and into the transaction’s comparison copies.
Why:
Native
git commitin the shadow reads the conclusion state the real worktree holds at invocation. Landing later compares the real entries with these copies, so an entry changed by another command after invocation is kept (conclusion_cleanup.rsholds the landing side). Pseudorefs of a reftable repository live in the ref store and are copied withupdate-ref(src/shadow-repository/shadow-conclusion-state.ts). - config_
error - The one failure type for rejected configuration content or files.
What:
The one failure type for rejected
cli-git.config.jsonccontent or files. Why: Callers render a single configuration diagnostic without parsing message text. - config_
file - Repository-root discovery and bounded reading of
cli-git.config.jsonc. What: Find and readcli-git.config.jsoncat a repository root. Why: Configuration is one data file at one fixed place; legacy executable configuration is reported for migration and never read, run or silently ignored. - config_
loading - Lazy configuration loading for known inspection versus mutation/ambiguous commands. What: Policy-configuration loading decisions before expensive policy initialization. Why: Known inspection forms keep the fast path; mutations, aliases and ambiguous options require configuration.
- config_
parse - Whole-document JSONC validation into typed settings.
What:
Turn
cli-git.config.jsonctext into validated typed settings. Why: Every unknown key, wrong type or ambiguous duplicate is rejected before any policy or transaction reads a setting. - config_
rules_ file - The
rulesFileoption: a repository-relative name that stays inside the repository. What: TherulesFileoption ofsecurity/forbidden-strings: which files in the repository hold the scanner’s private rules, each written relative to the repository’s top level. This module checks one path;config_policies.rsreads the string and array forms and calls it for each path. Why: The user decided on 2026-10-05 that configuration names the rules file first, thenFORBIDDEN_STRINGS_RULES, then the default file. A configured name is checked when the configuration is read: it must stay inside the repository by its words alone, so a configuration cannot point the scanner at a file elsewhere on the machine. A name that is missing on disk is not a configuration error; loading the rules reports it when the policy runs. - config_
schema - Typed settings and their defaults.
What:
The typed settings a validated
cli-git.config.jsoncproduces, with defaults. Why: The policy engine and transactions read plain typed values, never JSON. - config_
snapshot - One
git config --listread, parsed, and the lookups the commit path makes against it. What: One read of the whole Git configuration (git config --list -z --show-scope), parsed here, and the lookups the commit path makes against it instead of startinggit configfor each key. Why: Everygit configstart costs a process, and a wrapped commit asked Git the same configuration several times (core.hooksPath, the hook switches, the maintenance keys, the ref storage). Git lists entries in precedence order (system, global, local, worktree, thenGIT_CONFIG_COUNT,GIT_CONFIG_PARAMETERSand-cvalues, all in thecommandscope), so the last entry of a key is the valuegit config --getprints. A lookup answers only when it can reproduce Git’s own answer exactly; any value it does not model (~and%(prefix)paths, unit suffixes, hexadecimal, a bad boolean) isLookup::Unhandled, and the caller starts Git for that key as before. - content_
digest - The length and hash of an index snapshot that a landing record keeps.
What:
The length and a 64-bit FNV-1a hash of a file’s bytes,
the evidence a landing record keeps about each index snapshot.
Why:
The index snapshots of a landing are written without a sync.
A power loss can leave one of them with fewer bytes than were written,
or with bytes that never reached the disk,
while the synced landing record and the moved branch survive.
Recovery installs the post-index as the real index,
so it compares the file with this evidence first and never installs a snapshot that differs.
FNV-1a is specified by its two constants and needs no dependency,
and it is stable across Rust versions and builds,
which
std::hash::DefaultHasheris not; it detects damage and is not meant to resist someone who edits the file on purpose. Measured in a release build over a 521,631-byte index (5,000 entries): 0.38 ms per file. - dependent_
version_ bundled - Which development dependencies each dependent bundles.
What: Decide which development dependencies each dependent bundles, reading source
only where an edge could carry a raised version.
Why: A development dependency ships inside a dependent only when its non-test source
imports it. Scanning every package’s source on every commit would read the whole
workspace, so, as in the incumbent (
dependent-bump-workflow.ts:176-278), only dependents that could reach a raised package if every development dependency were bundled are scanned, and only for edges that lead toward a raised package. - dependent_
version_ content - The repository content the dependent-version planner reads, and its two failure kinds.
What: The repository content the dependent-version planner reads, behind one small
interface, and the two ways planning can fail to finish.
Why: The planner is pure with respect to the repository: it never starts Git and never
reads the filesystem. The policy’s wiring answers from the wrapper’s candidate
layer, the release path from the worktree, and tests from memory. Failures are
reported by cause: content that could not be read is
content-unavailable; content that was read but that the planner cannot use ispolicy-incomplete. - dependent_
version_ graph - The dependency walk and the plan of dependent bumps.
What: Walk the workspace dependency graph from the packages whose version was raised and
plan a patch bump for every publishable package that reaches one of them.
Why: A dependent’s published contents change when a dependency it ships with changes,
so its version must move too. This is the incumbent’s closure and filter
(
dependent-version-bump.ts:183-350): edges to names outside the workspace and self edges are ignored, the walk passes through unpublished packages and cycles, and only publishable, versioned, not-yet-raised packages are bumped. - dependent_
version_ imports - Whether source text imports a package.
What: Decide whether source text imports a package or one of its subpaths.
Why: A development dependency is bundled, and so carries a bump, only when non-test
source imports it. This is the incumbent’s specifier scan (
source-imports.ts:59-264): a quoted specifier equal to the name, or starting with the name and/, directly afterfrom,import,import(orrequire(. Every character it inspects is ASCII, so scanning UTF-8 bytes decides exactly what scanning UTF-16 units decides. - dependent_
version_ lifecycle - The dependent-version planner’s view of a prepared candidate state.
What: The dependent-version planner’s view of a prepared candidate state: every tracked
file of the private index, its bytes with this invocation’s corrections laid over
them, and the
HEADcommit’s copy of a file. Why: The planner reads workspace manifests, the registry configuration and source files wherever they are, not only the candidates; the installed wrapper’s policy reads them as tracked files of the candidate state (trackedFiles,bytes,headBytes). Everything goes through the store the candidates were prepared in, so a direct fix’s later pass reads what its earlier passes corrected. - dependent_
version_ manifest - Strict JSON reading of a manifest’s name, version and dependency names.
What: Read the facts a dependent bump needs from one
package.json: its name, its version, and the workspace names it depends on. Why: The incumbent parses manifests withJSON.parse(manifest-text.ts:130-161). This module accepts exactly JSON: it parses with the crate’s JSONC parser and first refuses the two things JSONC adds, comments and trailing commas. Keys followJSON.parse: the last duplicate wins. A manifest is decoded as strict UTF-8 after one optional byte-order mark, as the incumbent’s fatalTextDecoderdoes. - dependent_
version_ paths - Which paths the dependent-version planner reads: manifests, configuration, source.
What: Which repository paths the dependent-version planner reads: workspace manifests,
the registry configuration, and a package’s non-test source files.
Why: The planner selects these from the tracked-path list itself, so the provider needs
no pathspec support. The rules are the incumbent’s:
isWorkspaceManifestPath(dependent-version-bump-policy.ts:66-73), the manifest pathspec:(glob)package/*/*/package.json,PNPR_CONFIG_PATH(publishable-names.ts:10) andisNonTestSourcePath(source-imports.ts:38-55). - dependent_
version_ plan - The dependent-version plan with exact manifest bytes before and after.
What: Plan patch bumps for publishable dependents of packages whose version differs from
the base, with the exact manifest bytes before and after each bump.
Why: This is the one planner behind both entry points: the commit-time policy and the
release workflow’s ripple, which will call the native wrapper’s direct fix. It is
the incumbent’s
planWorkspaceBumps(dependent-bump-workflow.ts:280-388) over the content seam: it starts no Git process and reads no file. - dependent_
version_ policy - The decision and findings of
mono/dependent-version-bump. What: The decision ofmono/dependent-version-bump: when to plan, and the findings a plan becomes. Why: The plan is a pre-commit step. Another forwarded command such asgit addcould not apply its patches, so it plans only forgit commitamong forwarded commands, and forgit cli-git checkandgit cli-git fix; and only when a workspace manifest is modified, so ordinary commits read nothing. Each stale dependent is one finding whose patch rewrites a tracked manifest that may not be a candidate, which adds that manifest to the commit (dependent-version-bump-policy.ts:178-235). - dependent_
version_ publishable - Publishable package names from the generated registry configuration.
What: Read the publishable package names from the generated registry configuration
(
package/config/pnpr/config.yaml). Why: Only published packages receive a dependent bump. The generator writes each name as a single-quoted YAML list item on its own line underpackages:, so a line scan reads exactly what it wrote, as the incumbent does (publishable-names.ts:38-81). Lines are trimmed with JavaScript’sString.prototype.trimset of characters. - dependent_
version_ release - Patch bumps of plain release versions and
JSON.stringifyquoting. What: Patch-bump a plainmajor.minor.patchrelease version, and quote text the wayJSON.stringifydoes. Why: A dependent receives a patch bump. Only a plain release can be bumped automatically; anything else (a prerelease, build metadata, a leading zero, a missing component) is reported for a bump by hand, with the incumbent’s message (dependent-version-bump.ts:87-177). The patch component is incremented as a decimal digit string, so it is exact at any size. - dependent_
version_ states - Every workspace manifest at both states, and which versions were raised.
What: Read every workspace manifest at the candidate state and at the base, and decide
which packages’ versions were raised.
Why: The plan starts from these facts. The order of failures follows the incumbent’s:
every manifest is decoded before any is parsed (its reader decodes while listing,
dependent-version-bump-policy.ts:82-92), and each manifest’s base text is parsed before its current text (dependent-bump-workflow.ts:150-174). - dependent_
version_ text - The byte-preserving rewrite of a manifest’s top-level version.
What: Rewrite the top-level
versionvalue in a manifest’s text, leaving every other byte unchanged. Why: The bump is delivered as a full-content patch, so indentation, key order, line endings and the trailing newline must survive exactly. This is the incumbent’s scanner (manifest-text.ts:184-333) over bytes: it tracks{/[depth outside strings, finds the first"version"key at depth one, and requires its raw value to be exactly the JSON quoting of the expected version. - diagnostic_
log - Line-oriented debug and warning diagnostics on standard error.
What:
Line-oriented debug and warning diagnostics on standard error,
switched like the
incumbent’s console sink.
Why:
The incumbent prints debug records only when
MONOCHROMATIC_VERBOSE=trueand warnings unlessMONOCHROMATIC_WARN=false(package/logger/core/src/sink/console.ts), and its spec requires that diagnostics never corrupt a JSONL stream: each diagnostic is one complete line written in one call (SPEC.md, “Debug logging”). - diagnostics
- JSONL event rendering for wrapper-made diagnostics. What: Render wrapper diagnostics as the JSONL events callers already parse. Why: Agents and hooks read cli-git’s event stream line by line; every wrapper-made event must be one compact JSON object per line with stable field order.
- direct_
fix - The direct fix:
converge corrections in memory,
then install them.
What:
git cli-git fix: run policy passes over the selected files, apply the corrections they propose in memory until a pass proposes none, then install the corrected files into the worktree when that last pass is clean. Why: The installed wrapper’sdirect-fix.tsanddirect-fix-convergence.tsdecide the behaviour: only the last pass is reported, a fix summary follows it only when a file changed, nothing reaches the worktree unless the last pass exits 0, and a fix that cycles or keeps changing ends with one engine failure. The bounded loop itself ispolicy_convergence::converge; this module supplies its passes. Corrections replace whole file contents in memory instead of being staged into the private index, so a pass starts no Git process for them. - direct_
fix_ install - Installing a direct fix’s corrections into the worktree,
all or none.
What:
Install the corrections a converged
git cli-git fixsettled on into the worktree, all of them or none, and never touch the index. Why: The installed wrapper’sdirect-fix-install.tsdecides this protocol: every corrected file must still hold the bytes the fix read, each replacement is written beside its file and renamed over it, a copy of the old file is kept until the end, and the real index must hold the same bytes afterwards. Any failure restores every file already replaced. Failure messages name no file, because a pathname can hold the text a scan exists to keep out of output. - discard_
audit - The append-only record of every command form that can discard uncommitted state. What: The append-only record of every command form that can discard uncommitted state: rejected, allowed because the worktree was clean, exempt, or run with the escape hatch. Why: When uncommitted work vanishes, nothing said which command did it, and the person who lost it blamed the wrong one. One line per decision, with the time, process, parent process, directory and arguments, makes a loss attributable. Writing the record never changes the outcome of the command.
- doctor
git cli-git doctor: the traditional hooks a repository holds, which are unsupported. What:git cli-git doctor: list the traditional hooks a repository holds and the external hooks its Git configuration defines, and note the retiredhooks.concurrentCommitskey. Why: Traditional hooks (executable scripts in<git-common-dir>/hooksor in acore.hooksPathdirectory) are unsupported, and their behavior with the wrapper is undefined. The commit path warns whencore.hooksPathis configured but never scans a directory, because a scan costs file system calls on every commit; this command is where the scan happens, on request. Exit status 0 means none were found, 1 means at least one was (a configured hook counts), and 2 means the repository could not be located. A hook file that is byte for byte the shimgit lfswrites for its own name is expected and harmless, because the wrapper does the Git LFS upload itself and starts Git with traditional hooks off; it is explained apart from the other hooks and does not make the exit status 1.- doctor_
config - What
git cli-git doctorreports from the configuration: configured hooks and the retiredhookskey. What: Whatgit cli-git doctorreports from the configuration: the external hooks Git configuration defines, and the retiredhooks.concurrentCommitskey. Why: The wrapper runs no external hook, so a configured hook is a finding the author must act on, and ahookskey incli-git.config.jsoncis accepted for older files but means nothing; doctor says both plainly. The commit path prints the first as a warning from the snapshot it already holds; doctor lists it on request. - effective_
target - Worktree target classification for worktree-enforcing policies. What: Classify the worktree an invocation targets for worktree-enforcing policies. Why: Linked-worktree safeguards discipline a person’s repositories, not the disposable clones a tool keeps in its own cache.
- empty_
tree - The empty tree’s object name under each known object format.
What:
The object name of the empty tree under each object format Git supports,
known without asking Git.
Why:
The invocation capture needs the empty tree as the baseline of an unborn branch.
Git hashes the framed bytes
tree 0followed by a NUL byte, so the name depends on the object format alone and never on the repository’s contents. Spawninggit hash-object -t tree --stdinfor a constant cost a process on every commit. - entry
- The per-invocation decision the thin executable performs.
What:
Decide what one
gitinvocation of the native wrapper does, and perform it. Why: The executable stays thin: it gathers process facts, asks this module for an action, and performs it. The decision itself is testable without replacing the test process. - escape_
hatch - Spellings of the wrapper-only escape hatches shared by several modules.
What:
The spellings of the wrapper-only escape hatches several modules share.
Why:
A command module puts its own hatch first in the flag list it hands the option
tokenizer,
and the control table lists the same spellings;
one constant per
spelling keeps both in step.
Removal is not done here:
wrapper_invocation.rsdeletes wrapper tokens by the positions the tokenizer reports. - event_
hints - The intent-guessing hint that follows a finding, an engine failure or a notice. What: The hint that follows a finding, an engine failure or a warn-unsafe notice: a guess at what the person was trying to do, and the concrete next step. Why: A block that only states a rule leaves the next move to guesswork. Every block and every divergence from real Git’s behavior names the likely intent and a way forward, so a rejected command is a step toward the goal. Messages built with their own hint (the discard, branch, add, root and commit-only rejections) are listed as self-hinted, and a guard test fails when a code has neither.
- event_
path - A pathname as events report it:
text,
plus base64
pathByteswhen it is not UTF-8. What: A repository path as an event reports it: readable text, and the exact bytes as well when those bytes are not UTF-8. Why: Git allows any bytes in a file name, and a JSON string can hold only text. An event keepspathas text with replacement characters, so every existing reader works unchanged, and adds an optionalpathBytesfield, base64 encoded, only for a name that is not UTF-8, so a reader that needs the file can still find it. This is option C ofdoc/planning/cli-git-rust-open-decisions.md, section “Non-UTF-8 paths in events and journals”, which the owner adopted. - external_
hooks - The configured external hooks, read from the snapshot, and the loud report that none runs.
What:
The external hooks a repository configures (
hook.<name>.eventwithhook.<name>.command), read from the configuration snapshot, and the loud report that none of them runs. Why: The wrapper supports no external hook program at all: every real-Git start of the wrapper switches every hook event off (hooks_off.rs), and the behaviors a hook used to provide are declared natively incli-git.config.jsonc. A configured hook that silently never ran would look like a broken check, so a commit prints oneconfiguration-warningevent naming the keys and the events, decided from the snapshot the commit already holds (no process start), andgit cli-git doctorlists the same hooks. - filesystem_
identity - The filesystem identity recorded with every real
index.locka transaction creates. What: The filesystem identity recorded with every realindex.locka transaction creates, spelled exactly as the incumbent’s@monochromatic-dev/fs-id-corespells it. Why: Recovery removes a realindex.lockonly when its device, inode and filesystem identity all match the journal; the incumbent compares the recordedfsIdwith the one it resolves now, so both wrappers must resolve the same text for the same filesystem (package/fs-id/core/src/platform-resolvers.ts,parsers.ts). - filesystem_
identity_ fsid - The Linux
f_fsidof a path asked of the kernel in-process instead of by startingstat. What: Thef_fsidof the filesystem holding a path, asked of the kernel in-process and spelled exactly as GNUstat --file-system --format=%ispells it. Why: A filesystem without a UUID (overlayfs, tmpfs) is identified by this text, and the identity of a realindex.lockis resolved again when the lock is released. Startingstatfor it made that release depend on the host granting a process start, so a host that refused every start left the lock behind and a plaingit addstuck on it; the same call in-process needs no process, and also saves thestatstarts of every commit on such a filesystem. - filesystem_
identity_ mount - The Linux filesystem UUID of a path read from the mount table and the udev database instead of
findmnt. What: The Linux filesystem UUID of the filesystem holding a path, read in-process from the kernel’s mount table and the udev database instead of by startingfindmnt. Why:findmnt --target <path> --output=UUID --noheadingsis the most expensive process of a wrapped commit, and the recorded identity must stay byte-identical to what it prints; every shape this module does not reproduce exactly is reported as unknown so the caller spawnsfindmntas before.doc/planning/cli-git-spawn-and-sync-reduction.md(section “Filesystem identity withoutfindmnt”) holds the algorithm read from util-linux and the measurements. - forwarded_
command - The Git command a forwarded invocation runs,
after ordinary alias expansion.
What:
The Git command a forwarded invocation actually runs,
after ordinary alias expansion.
Why:
Git expands an alias only when no built-in has that name,
reads it from
alias.<name>oralias.<name>.command, splits it withsplit_cmdlinequoting, lets its leading words be global options, and expands again when the result is an alias. Index-writer coordination and the commit rules decide from the expanded command, sogit -c alias.c=commit cis a commit (src/forwarded-command.ts). - forwarding
- Starting real Git with unchanged arguments, streams and exit status. What: Start real Git with the caller’s exact arguments, streams and environment. Why: The wrapper adds policy around Git; it must never change what Git receives or what the caller observes from Git.
- git_
builtins - The names of the commands built into Git 2.56.0.
What:
The names of the commands built into Git 2.56.0,
and the test “is this word one
of them”.
Why:
Git never expands an alias whose name is a built-in command,
so a built-in word
is the command that runs.
Any other word may be an alias for anything,
including
worktree add, and resolving aliases is not ported; the caller needs to know which of the two it is looking at without starting Git. - git_
metadata - Captured read-only real-Git queries over raw bytes. What: Run a read-only real-Git query and capture its output as raw bytes. Why: Repository facts (top level, Git directory) come from Git itself, and the paths it prints need not be UTF-8.
- git_
system_ config - The system configuration file a real Git executable reads, learned once per executable identity.
What:
The system configuration file that one real Git executable reads when
GIT_CONFIG_SYSTEMis not set, learned once per executable identity and cached beside thegit versioncache. Why: Git compiles the path in (ETC_GITCONFIG,/etc/gitconfigfor a build with prefix/usr, and under the prefix for any other build, resolved against the executable’s own location in a runtime-prefix build), so no reader may assume/etc/gitconfig.git var GIT_CONFIG_SYSTEMprints the path the executable would read, with the environment’s own overrides removed from its start, so the path is a property of the executable’s bytes and location, which the identity ofgit_version_cachecaptures. An executable without an identity (a script, a shim) or an answer that is not an absolute path is unknown, and the readers then leave the question to Git. - git_
version - The Git version check run once before work that depends on Git’s behavior.
What:
The real Git version check,
run once per invocation before work that depends on Git’s
behavior:
a commit,
hooks and locks.
Why:
The native tables and protocols follow Git 2.56.0,
and the
owner settled on 2.55.0 as the oldest release that runs the swap,
so 2.55.0 or newer counts as supported and that read-only commands are never checked
(
doc/handover/cli-git-rust-implementation.md, “Adopted without a question”). - git_
version_ cache - The private per-user cache of the version text, keyed by the Git executable’s identity.
What:
A private, per-user cache of the text
git versionprints, keyed by the identity of the Git executable that printed it. Why: Every commit startsgit versionto learn whether Git is new enough. The text is fixed by the executable, so while the executable is byte-for-byte the same file the answer is the same. The identity is the canonical path, size, modification time, change time, device and inode: an upgrade replaces the file (new inode) or rewrites it (new size, times), so a replaced Git is noticed on the next start and a stale entry is never trusted. The cache is used only for a real compiled executable namedgit: a script, or a shim such as a version manager’sgitlink to a dispatcher, picks another Git by directory or environment, so its own file proves nothing about the version it will run. The cache lives in the user’s cache directory because the version check runs before any repository is known. - global_
arguments - Git 2.56.0 global-argument boundaries over unchanged operating-system strings. What: Locate Git 2.56.0 command boundaries without reconstructing native arguments. Why: Policy stages need the command index, but Git remains authoritative for option validation and -C semantics.
- holder_
budget - One time budget for everything a lock holder does under the push lock.
What:
One time budget for everything a lock holder does while it holds the push lock.
Why:
A waiter gives up after a limit, and a holder that is still working inside its rights must not make a waiter give
up. Every bounded step of the holder (the LFS upload, the push, the fetches and merge pushes of a reconcile, the
Git queries between them) used to have a full deadline of its own, so the holder’s total had no bound that the
waiter’s limit could be derived from. While a budget is in force, each step’s deadline is the smaller of its own
deadline and what is left of the budget, and no step starts once the budget is spent. The waiter’s limit is then
the budget plus what one in-flight step may overrun it by, plus a margin (
bounded_push::lock_wait_limit). The budget belongs to the thread that holds the lock; a process holds one push lock at a time. - hooks_
off - Every real-Git start of the wrapper carries
core.hooksPathpointing at a directory that never exists. What: Start the real Git with every hook switched off. Why: The wrapper supports no external hook program: not a traditional hook (an executable file in<git-common-dir>/hooksor acore.hooksPathdirectory;git lfswrites its four shims there again whenever it cleans a file) and not a hook defined in the configuration (hook.<name>.eventwithhook.<name>.command). Every real-Git start of the wrapper therefore carries-c core.hooksPath=<a directory that never exists>, which finds no hook file, and-c hook.<event>.enabled=falsefor every hook event Git knows, which switches every configured hook off whatever its name.core.fsmonitoris left as configured: it is a program Git starts for its own feature, and the event switches do not reach it (measured on Git 2.56.0, doc/planning/cli-git-no-self-invocation-dispatcher.md). They follow every global option of the caller and precede the subcommand, so no-cor--config-envof the caller can switch a hook or the hooks path back on. The options are part of the argument vector, so they add no system call and no process start. The wrapper injects no hook of its own, sopre-pushis switched off like every other event. - index_
flags - A read of the assume-unchanged and skip-worktree marks of an index file.
What:
A read of the per-entry flags of an index file that decide whether the index may seed an explicit-path commit:
whether any entry is marked assume-unchanged, skip-worktree or intent-to-add.
Why:
A private index copied from the real index and reset to the base keeps those marks, and
git add --all -- <path>then skips a marked file the commit named (assume-unchanged) or refuses it (skip-worktree), where Git’s own partial commit and the previous flow staged it; an intent-to-add entry of a tracked empty file survives the reset and is then left out of the tree, so the commit loses that file. Finding a mark needs the whole file, and a Git start for it would add a process to every commit, so the entries are read here; anything this reader does not fully understand answers “unreadable”, and the caller then builds the index the slower, always-correct way. The layout is the one in Git’sgitformat-indexdocumentation: a 12-byte header, entries (versions 2 to 4), then extensions and a trailing checksum. - index_
lock_ evidence - Evidence about a foreign
index.lockand the verdict it supports. What: Evidence about a foreignindex.lockand the verdict it supports: proven alive, dead, or evidence-free. Why: A live owner is proven by a process holding the lock open (device and inode), or by Git’score.lockfilePidfile naming a running process that started no later than the lock changed. A PID file naming a gone process proves a dead owner. Everything else is evidence-free; an absent open holder never proves abandonment, because nativegit commitkeepsindex.lockwithout a descriptor through its hooks and editor (src/index-lock/index-lock-evidence.ts). - index_
lock_ holder_ parsers - Parsers of
lsof -F pcDirecords and Restart Manager lines, matched by device and inode. What: Parsers of the holder-scan output that is not/proc:lsof -F pcDirecords and the Restart Manager script lines, both matched to a lock by device and inode. Why: The parsers are pure text functions, so their controls run on every host, whichever scan produced the text (src/index-lock/index-lock-holders-darwin.ts,-win32.ts). - index_
lock_ holders - Processes holding a lock file open,
found by device and inode.
What:
Processes holding a lock file open,
found by device and inode:
/proc/<pid>/fdon Linux,lsofon macOS, the Restart Manager on Windows. Why: An open descriptor proves a live owner. Matching by device and inode, never by path text, means bind mounts and renamed paths cannot fake or hide a holder. Processes the scan cannot read are partial evidence (src/index-lock/index-lock-holders-linux.ts,-darwin.ts,-win32.ts). - index_
lock_ wait - Waiting for a foreign index.lock under the classification rules and Git’s backoff schedule.
What:
Waiting for a foreign
index.lockunder the classification rules: a proven-alive owner is waited for up to this process’s wait limit (CLI_GIT_LOCK_WAIT_SECONDS, 30 minutes by default), a dead or evidence-free owner is retried on Git’s backoff schedule until the unproven budget runs out. Why: An index writer must not run beside a landing, and a lock nobody can prove alive must not block forever. Nothing here deletes a lock; the caller decides what the failure means (src/index-lock/index-lock-wait.ts). - index_
lock_ wait_ host - The host side effects of the index-lock wait:
/proc reads,
sleeps,
randomness and standard error.
What:
The host side effects of the index-lock wait:
/procand Git-free lock reads, real sleeps, operating-system randomness and standard error. Why:WaitEffectsis the seam the controls replace; this is what the executable runs. - index_
writer_ commands - Index-writer classification and the commands that coordinate with a landing.
What:
Whether a resolved forwarded command writes the real index.
Why:
Index writers take the landing lock so they never interleave with a landing commit.
The set follows
SPEC.md“Index-writer coordination”:add,rm,mv,restore --staged,resetexcept--soft,stash,checkout,switch,merge,rebase,cherry-pick,revert,apply --cachedandapply --index,update-index,read-tree,am,pullandsparse-checkout. Options count only before Git’s--; long options match in full or as the abbreviations Git accepts (src/index-lock/index-writer-commands.ts). - index_
writer_ coordination - Coordination of a forwarded index writer:
the landing lock,
the wait for a foreign lock and the lease.
What:
Coordination of a forwarded index writer with concurrent landings:
the landing lock is taken
after recovery,
a foreign
index.lockis waited for under the classification rules, and the lock is handed to real Git through the landing lease until Git returns. Why: An index writer must not interleave with a landing commit, and a nested wrapper invoked by the coordinated Git (for examplegit rebase --exec 'git add') must not wait on the lock its ancestor holds (src/index-lock/index-writer-coordination.ts). - index_
writer_ forward - Running a coordinated forwarded index writer:
the landing lock held while real Git runs as a child.
What:
Running a forwarded index writer under its landing lock:
coordinate,
run real Git as a child
that inherits the landing lease,
release the lock once Git returns.
Why:
Replacing the process would drop the lock with the process image,
so a coordinated writer
waits for Git instead.
The nested wrappers Git starts see the lease and do not wait on it
(
src/forward-real-git.ts). - invocation_
config - Configuration of the repository one invocation selects.
What:
Load the policy configuration of the worktree one invocation runs in,
and render
the events configuration loading can produce.
Why:
Both wrapped Git commands and
git cli-git check/fixneed the same answer: what does thecli-git.config.jsoncof the worktree Git reported say. - iso_
time - UTC timestamps in the exact form of JavaScript’s
toISOString. What: UTC timestamps in the exact form of JavaScript’sDate.prototype.toISOString:YYYY-MM-DDTHH:MM:SS.mmmZ. Why: Transaction owner records carrycreatedAtand capture recordsinvokedAtin this form; recovery orders dead transactions and the reservation grants turns by comparing these strings, so both wrappers must write the same shape. - journal_
record - One journal record written whole, followed by the syncs its durability level names.
What:
One journal record written whole, followed by exactly the syncs its durability level names.
Why:
Recovery reads each record of a transaction directory, and the tearing table in
doc/planning/cli-git-spawn-and-sync-reduction.mdshows which records a power loss may lose or tear without any change of outcome and which must reach the disk before the next step. Naming the level at the call site keeps every record’s sync decision visible, and the budget tests pin the resulting counts. - js_text
- Text operations with the exact semantics of JavaScript’s built-ins.
What:
Text operations with the exact semantics of the JavaScript built-ins the incumbent
applies to strings it shares with the native wrapper.
Why:
Durable strings such as process birth identities are produced by
.trim()in the incumbent; the native wrapper must produce byte-identical strings, and Rust’sstr::trimuses a different whitespace set (it trims U+0085 and keeps U+FEFF). - json_
record - Compact JSON records read and written with the incumbent’s
JSONsemantics. What: Reading and writing the compact JSON records both wrappers share, with the incumbent’sJSON.parseandJSON.stringifysemantics. Why: Lock, owner, journal and capture records written by either wrapper are read by the other. A record must be read the way the incumbent reads it (numbers are doubles, a whole number in safe-integer range is an integer, the last duplicate key wins) and written with the same field order and escaping. - landed_
disposal - Disposal of a landed transaction: shadow removal, directory removal, then landed-capture pruning.
What:
Disposal of a landed transaction: its shadow repository, then its transaction directory,
then the landed-capture records no published transaction still needs.
Why:
The incumbent’s
commit-transaction-workspace.tsruns these steps in this order. A shadow never outlives the directory that names it, and pruning runs last, so the last transaction to finish sees no other transaction pinning a landed record. A failed removal stops the sequence, so recovery still finds the directory and its records. - landing_
attempt - One landing attempt inside the landing critical section, and its outcome.
What:
One landing attempt inside the landing critical section:
the real
index.lock, the branch-switch and head-moved checks, object migration, the post-index,landing-<n>.json, the compare-and-swap with the nonce reflog message,ref-updated.json, the index install, theindex-installedmarker and conclusion cleanup. Why: A landing is serial under the landing lock and the realindex.lock, and it journals every state so recovery can finish or discard a landing that died anywhere inside (src/policy-engine/commit-landing.ts). The caller takes the landing lock, checks the starvation reservation and replays a moved target; the caller’s post-landing step disposes of the transaction directory. This module never runs hooks, the editor, signing, network operations or the hook lock. - landing_
findings - The
concurrent-commitfinding for a landing that landed nothing. What: Theconcurrent-commitfinding for a landing that landed nothing becauseHEADwas switched or the target moved. Why: Both leave the ref, the real index and the worktree bytes unchanged and end the commit with exit status 1 (src/policy-engine/commit-landing-findings.ts). This module only builds the event and names the exit status; the commit path that renders it is a later slice. - landing_
index - Post-index a landing installs, computed against the real index at landing.
What:
The real index a landing installs, computed against the real index as it is at landing,
never against the invocation-time copy.
Why:
A landing that copied the invocation-time index would erase another process’s staging
and stage a revert of content that already landed. Index mode takes the exact private index only
when nothing was restaged; otherwise it merges the landed entries path by path, and explicit-path
mode resets only the committed paths and the guarded paths still holding their invocation entry
(
src/policy-engine/commit-landing-index.ts). - landing_
invariant - The check that a landing changed no staged entry outside the paths it committed. What: The check that an explicit-path landing changed no staged entry outside the paths it committed. Why: Staged work from other sessions once vanished and nothing said which step removed it. The landing computes its post-index from a copy of the live index and resets only the committed and guarded paths, so every other entry must read the same afterwards. The check compares the two indexes and warns, naming the paths, when it does not; it never changes the landing, whose outcome is already decided.
- landing_
location - The landing location a forwarded index writer coordinates with:
the real index and the registry.
What:
Where a forwarded command’s landing lock and real index live:
one
git rev-parsethat reports the git directory, the index Git would use, and the transaction registry. Why: An index writer coordinates with landings through the registry’s landing lock and the real index’s lock. A command outside a repository, or one writing another index, coordinates with nothing (src/index-lock/landing-location.ts). - landing_
objects - Migration of a prepared commit’s shadow objects into a kept pack of the real store.
What:
Migration of a prepared commit’s shadow-only objects into the real store as one pack,
optionally kept by a
.keepfile naming the transaction. Why: The real target may name the commit only after the compare-and-swap, so its objects must be in the real store first, and the keep stops prune or repack from removing them mid-landing (src/policy-engine/commit-landing-objects.ts). - landing_
support - Landing target comparison, nonce reflog message and compare-and-swap.
What:
Target comparison, the nonce-bearing reflog message and the compare-and-swap a landing uses
to advance its target.
Why:
The reflog subject carries the transaction nonce, so recovery can tell whether the target
advanced to this landing even after a crash (
src/policy-engine/commit-landing-support.ts). The swap runs in the owning worktree’s context, which also writes the realHEADreflog. - lexical_
path - Lexical path normalization.
What:
Lexical path normalization, the way Node’s
path.resolvedoes it. Why:git cli-git doctorscans each hooks directory once, so one directory named two ways (./hooks,hooks/,a/../hooks) must compare equal without touching the file system. - lfs_
bounded_ push - The auto-push’s bounded
git push, preceded by the upload, its pre-check and its explanation. What: Run one boundedgit pushof the auto-push, preceded by the Git LFS upload when the repository has one. Why: The auto-push and its reconcile pushes publish refs, so they upload the LFS objects the pushed commits point to exactly as a typedgit pushdoes. The wrapper is the parent of both children and captures their output, so it owns the explanation: a missinggit-lfsis refused before anything starts, a failed upload ends the attempt with the full explanation after the program’s own output, and the push is not started. The push carries no hook, and no process other than Git andgit-lfsis started for this, none of them the wrapper. - lfs_
interactive - The Git LFS step before a push the caller typed: pre-check, plan and upload with the caller’s streams.
What:
The Git LFS step before a push the caller typed: the pre-check, the plan and the upload, with the caller’s streams.
Why:
The parent owns the whole step, so a refusal, a plan that cannot be worked out and a failed upload are all explained
by the process that saw them, and the push is not started after any of them. The configuration the plan reads comes
from the snapshot the wrapper already took when it planned the invocation, so a push whose repository is known costs
no Git start for it; without a snapshot one
config --get-regexpstart reads it. - lfs_
object_ plan - The upload plan narrowed to the objects the push introduces, or to nothing.
What:
Narrow an upload plan from “scan these refs with
git-lfs” to the exact objects the push introduces, decided in process, or to nothing. Why: Most pushes add no LFS object, and then nogit-lfsprocess and no request to the LFS server is needed. The tips come from the plan’s refs; the commits already known to the destination are the remote’s tracking refs when the plan trusts them, else none (the whole history of the pushed refs is scanned, which keeps the case where the tracking refs may not describe the destination correct). When the readers decline, or an object the scan found is missing from.git/lfs(wheregit-lfs pushwords the failure and honourslfs.allowincompletepush), the plan stays as it was andgit-lfsscans. - lfs_
output_ mask - Hides the credentials inside URLs in the text relayed from
git-lfs. What: Hide the credentials inside URLs in the text the wrapper relays fromgit-lfs. Why:git-lfsnames the LFS server in its errors, and anlfs.urlwritten ashttps://user:token@host/...carries the token into every one of them. The wrapper relays that text to the caller’s terminal and, for the auto-push, records it in files, so the userinfo of every URL is replaced at the one place where the text leaves the wrapper. The authority of a URL ends at the first/,?,#,\, quote, angle bracket, backtick or white space, and its userinfo ends at the last@before that, so an@in a path, a query or a bracketed IPv6 host is left alone. - lfs_
pointer - Whether a blob is a Git LFS pointer, decided as
git-lfsdecides it. What: Whether a blob is a Git LFS pointer, decided the waygit-lfs3.8.1 decides it when it scans a push. Why: The upload step finds the LFS objects the pushed commits introduce without startinggit-lfs. A blob counts when the scanner would hand it to the transfer queue: smaller than the cutoff (lfs/scanner.go,BlobSizeCutoff), a header that namesgit-media,hawserorgit-lfs(lfs/pointer.go,matcherRE), lines in the orderversion,oid,sizewith a known version, ansha256:object name of 64 lower-case hexadecimal digits and a non-negative decimal size; a pointer of size zero is skipped byprepareUpload(commands/uploader.go). The path of the blob and.gitattributesplay no part. A spelling the port is not sure about (extension lines, non-ASCII edges) is a decline, so the caller falls back togit-lfsitself. - lfs_
pointer_ scan - The LFS objects a push introduces, found in process from the commits to be pushed.
What:
The LFS objects a push introduces, found in process: the pointer blobs that the commits to be pushed add.
Why:
git-lfs pushstarts a scanner (git rev-list,git cat-file,git ls-remote) and, when it finds anything, asks the LFS server about it; most pushes add no pointer at all, so the wrapper reads the commits itself with the in-process readers and startsgit-lfsonly when a pointer is found. The commits are those reachable from the pushed tips and not reached before a known-good tip (the remote’s tracking refs, when they describe the destination). Every commit is compared with each of its parents, so a pointer added in one commit and removed in the next is still found. Anything the readers do not fully understand, and any walk that grows past its limits, returnsNone, so the caller asksgit-lfs. - lfs_
pre_ push - When a push needs the Git LFS upload, and the texts the parent prints around it.
What:
When a push needs the Git LFS upload, and the texts the parent prints around it.
Why:
Traditional hook files are unsupported, so the
pre-pushshimgit lfs installwrites cannot be relied on. Without an upload step a push publishes pointer files whose objects the LFS server never received. The wrapper therefore runsgit-lfs push <remote> <refs>itself, as a child it owns, before it starts the push (lfs_upload.rs), only when the Git configuration holds LFS keys; the push carries no hook and no injected configuration, the wrapper never starts itself and writes no shell text (doc/planning/cli-git-no-self-invocation.md). - lfs_
push_ precheck - The check that
git-lfscan start, made before a push that is preceded by the upload. What: The check, before anything starts, that the Git LFS upload a push is about to run can start. Why: A push of a repository with LFS keys is preceded bygit-lfs push, which the parent starts. When no directory ofPATHholdsgit-lfs, one stat perPATHdirectory, with no process started, finds that first, and the parent says so with the full explanation. A push that bypasses the upload (--no-verify,GIT_LFS_SKIP_PUSH, or the switch-c hook.cli-git-lfs-pre-push.enabled=false) needs no program, so it is never refused here. - lfs_
server_ retry - Which failed
git-lfs pushruns are tried again (server errors only), after how long, and the notice. What: When a failedgit-lfs pushis tried again: the server-error classification, the delays and the notice. Why: The LFS server of a repository can answer HTTP 5xx for a moment, andgit-lfstreats such an answer as fatal for the run (lfshttp/errors.go: a status above 499 other than 501, 507 and 509 becomes a fatal error), so a push that was fine a second later fails. Uploads are content addressed, so a second run never sends an object twice. The retry is bounded twice over: a short fixed list of delays, and a classification from the program’s own output that never retries a refused credential, a missing local object, or any failure that is not a server error. - lfs_
upload - The parent’s bounded or interactive run of the Git LFS upload.
What:
The parent’s run of the Git LFS upload before a push:
git-lfs push <remote> <refs>as one child it owns. Why: Without an upload step a push publishes pointer files whose objects the LFS server never received. The wrapper runsgit-lfs pushitself, so a failure is explained by the process that saw it and the push, which then carries no hook, is never started. Nothing here starts the wrapper, writes shell text or injects Git configuration (doc/planning/cli-git-no-self-invocation.md). What to upload is decided inlfs_upload_plan. The child sees what the caller gave Git: the global-coptions travel asGIT_CONFIG_COUNTpairs,--git-dir,--work-treeand--namespaceas their variables,-Cas the working directory, and the real Git’s directory leads itsPATHso agitit starts is never the wrapper whilegit-lfsitself is the one the caller’sPATHfinds first. - lfs_
upload_ plan - What the Git LFS upload sends: the remote and the refs, read from the push and the configuration Git would read.
What:
What the Git LFS upload before a push sends: the destinations
git lfs pushtakes and the refs whose objects it uploads, with--allwherever a remote-tracking ref of this clone may not describe the destination, so that it cannot hide an object the destination lacks, and in git-lfs’s bounded form (no--all) everywhere else. Why:git lfs pushtakes refs and no ref-update lines, so the plan is read from the push’s own arguments with the same option table as the rest of the wrapper, and from the configuration Git itself would read for the parts the arguments leave out (the default remote,remote.<name>.push,remote.<name>.mirror, the push URLs,push.default). The configuration comes from the snapshot the caller already holds, or from one query. Refs that Git expands from a pattern are listed withgit for-each-refand matched here with a refspec’s own rule (*crosses/). The plan fails closed: a query that fails is an error the caller turns into a refusal, never into “nothing to upload”. Only a mirror push uploads every ref. - lfs_
upload_ settings - The configuration the Git LFS upload plan reads, and the default remote derived from it.
What:
The configuration the Git LFS upload plan reads, and the answers derived from it: the default remote, the branch
HEADnames, and whether a value reads as true. Why: The plan and the manual-push probe must agree on the destination of a bare push, so both read it here. The entries come from a snapshot the caller already holds or from oneconfig --get-regexp, never one start per key. - live_
lock - Waiting for a lock a live process holds: a slow-wait notice naming the holder and a configurable backstop. What: Wait for a lock another live process holds, with a visible notice and a generous backstop. Why: A holder that is alive but stuck (a hung hook, a stalled tool) would otherwise stall every later command with no output at all. After a short silence the waiter says whom it waits for and when it will give up, and past the backstop it fails closed: the command stops with the holder named and the lock left in place.
- lock_
holder_ facts - What a waiting notice says about the lock holder: its age, its command and the limits.
What:
What a waiter can tell its user about the process that holds the lock it waits for: how long the lock has been
held, what the holder runs, and which limits end the wait.
Why:
A silent or anonymous wait looks like a hang. The age is the time since the lock directory was published, which
is when the holder took the lock; the command comes from the kernel’s record of the holder (Linux
/proc; other systems name no command); the limits are the push deadline that stops the holder’s push and the wait limit that stops this wait. - maintenance_
decision - Automatic maintenance decision: config record parsing and the enable and detach rules.
What:
The decision behind automatic maintenance after a landing:
parsing
git config --null --get-regexprecords, and choosing whethergit maintenance run --autoruns and whether it detaches. Why: Nativegit commitruns this step after updatingHEAD; a landing replaces that commit with plumbing, so the pack count grows by one per commit unless this step runs. The rules mirror Git’sprepare_auto_maintenanceinrun-command.c, including JavaScript number semantics for values the incumbent reads withNumber(...). - maintenance_
hk_ config - Exact removal of retired hk hook registrations from one explicit Git configuration scope.
What:
Exact removal of retired hk hook registrations from one explicit Git configuration scope:
list every scoped key with
git config --list --show-scope, keep the sorted unique keys of that scope that start withhook.hk-, and unset each with--unset-all. Why: The retired hk installer wrote its hooks into Git configuration; this repository’s migration removes only that exact namespace and leaves every other key alone. - maintenance_
run - Running automatic maintenance after a landing: the config read, then
git maintenance run --auto. What: Running automatic maintenance after a landing: onegit configread for the maintenance keys (or the commit’s configuration snapshot), thengit maintenance run --auto --quiet, detached or not, in the real worktree. Its exit status is returned for the caller to ignore, as native Git does. Why: Every landing migrates its objects into one pack; without this step the pack count grows by one per commit and every later Git process searches every pack. Failures are logged and never fail the landed commit. - management
- The
git cli-gitdecision: help, retired commands, and directcheck/fix. What: Decide what onegit cli-git ...invocation does. Why: Help, refusals and retired commands need no repository;checkandfixvalidate their policy selection and configuration, then run one policy pass over the selected repository and report its events on standard output. - management_
arguments - The
git cli-gitargument grammar. What: The grammar ofgit cli-git <command>, the wrapper’s own management namespace. Why:checkandfixrun policies over an explicit scope;doctorlists the traditional hooks a repository holds;trust,untrustandstatusare retired with executable configuration and only explain that. - management_
selection - The refusal of a direct command whose
--policyselects a policy that is off. What: The--policyselection of a directgit cli-git checkorfix: the names turned into shipped policies, and the refusal of a selection that names a policy which is off. Why: A mistyped policy ID must stop the command instead of selecting nothing. A policy that is off runs nothing, so a run limited to it would exit 0 with no output and read as “checked and clean”. A CI step or a person who selected the policy by name must learn that it did not run, and whichcli-git.config.jsonckey turns it on. The commit, add and push lifecycles are not direct runs and keep skipping policies that are off. - manual_
push_ lifecycle - The manual-push candidate scope of one push: the commits and trees the destination lacks.
What:
The manual-push candidate scope of one
git push: the commits and trees the destination lacks. Why: The manual-push policies (final-newline,forbidden-strings) scan the content a push publishes. The incumbent (src/policy-engine/manual-push-lifecycle.ts) discovers the updates, bounds each by what its destination already has, and hands the scan to the policy engine; a failure stops the push with exit2. - manual_
push_ porcelain - Authoritative manual-push updates discovered by a private dry-run push and confirmed with ls-remote.
What:
The parser of
git push --dry-run --porcelainoutput, which tells the manual-push probe what a push would send. Why: The probe used to learn the update lines from a recording hook written as shell text. Git’s own dry-run report carries the same facts without any program of ours: for each destination aTo <url>line, then one tab-separated line per ref (<flag>\t<from>:<to>\t<summary>), thenDone. Withcore.abbrev=nothe summary of an update holds the full old and new object names (old..new, orold...newwhen forced); a new ref, a deletion and a refusal hold a bracketed word instead. - manual_
push_ probe - The updates a manual
git pushwould send, learned from Git’s own dry-run report and confirmed against the remote. What: The authoritative ref updates agit pushwould send, discovered by a dry-run push whose report Git writes itself. Why: The manual-push policies must scan exactly what Git would publish, so the probe asks Git:git push --dry-run --porcelainreports each destination and ref update (manual_push_porcelain), the local objects come from that report or from onerev-parse, andgit ls-remote --refsconfirms each destination value. A remote ref that changed between negotiation and confirmation stops the probe. No program of ours is started for this, and no hook or shell text is written: an earlier version recorded Git’s update lines with a#!/bin/shhook (doc/planning/cli-git-no-self-invocation.md). Every Git child here is bounded by the query deadline, so a remote that never answers cannot hold the push. - manual_
push_ range - Published-set and range computation for manual-push scans: which pushed commits a remote does not have.
What:
The commits a push newly publishes, relative to what each destination remote already has.
Why:
The manual-push policies scan only what becomes public. A pushed commit is already published when a
remote-tracking ref of the destination, or an authoritative prior value of an update to that remote,
reaches it (
src/policy-engine/manual-push-published.ts). The rest of the history is the scan’s range. - markdown_
exclude - The gitignore-syntax
excludepatterns ofmarkdown/autofix, matched as the linter matches them. What: Theexcludeoption ofmarkdown/autofix: gitignore-syntax patterns, relative to the repository’s top level, naming candidates the policy leaves alone. Why: The installed wrapper filtered candidates with theignorepackage before starting the linter (rewrite-candidates.ts,rewriteCandidates), so an excluded file never starts a linter and never fails for one. The linter evaluates its own copy of the option with theignorecrate’s gitignore matcher and a walk over ancestor directories (package/linter/monochromatic-lint/src/markdown_lfs_patterns.rs); this module uses the same matcher and the same walk, so both agree on every path. - markdown_
linter - The executable’s linter for that policy:
monochromatic-lintlinked in-process, guarded by a deadline, once per candidate. What: The linter the executable givesmarkdown/autofix:monochromatic-lint, linked into the executable and run in-process once per candidate, on a guarded thread with a deadline. Why: The owner’s rule is that the wrapper embeds the repository’s own tools and starts other programs as rarely as possible. The linter’s library entrymonochromatic_lint::run_embedded::fix_sourceis the executable’s--config --stdin --stdin-filename --fixrun without the program start (package/linter/monochromatic-lint/src/run_embedded.rs), so there is no program to find on a search path, no configuration file to write and no stream contract to parse. The child process used to be the wrapper’s kill boundary;markdown_linter_guard.rsreplaces it with a thread, a deadline and panic containment, which is a hang and panic boundary and not a crash boundary: a stack overflow or an abort in the linter ends the wrapper. The size of a finished result is checked against the former child’s output bounds (markdown_linter_output.rs), after the linter has produced it. The configuration is rebuilt from the policy’s options for each candidate, which costs a small JSONC document, so no state outlives the options it was built from. - markdown_
linter_ config - The one-rule linter configuration document.
What:
The linter configuration the
markdown/autofixpolicy handsmonochromatic-lint: one block that selects the policy’s rules for Markdown and MDX files, as JSONC text for the linter library’s in-process entry. Why: The plan’s contract (doc/planning/unified-linter.md, section “Distribution and consumers”) is a one-rule configuration of the policy’s own, so a configuration the repository keeps for its own linting never changes what the commit-time policy does. Each rule runs atwarn, so a finding the fixes leave is a warning finding and the run counts as usable, and only a run that could not finish fails. The document is built as a value and printed by the JSONC emitter, so no pattern text can break out of its string. - markdown_
linter_ guard - A job on a thread of its own with a deadline and panic containment. What: Run one job on a thread of its own and wait for it with a deadline, so a job that panics or never ends costs the caller one failed result, not the process. Why: The Markdown linter used to run as a child process, and the operating system gave the wrapper a free boundary: a child that hung could be killed and a child that crashed could not take the wrapper down. Linked into the wrapper, the linter shares its address space, so a boundary is rebuilt here from what the language offers, and it is a smaller one than a process gave: a hang and a panic are contained, a stack overflow, an out-of-memory abort or any other abort is not, and ends the wrapper like any other crash. The size of a finished result is bounded by the caller after the fact. The long-term answer is to run the linter in the resident service, outside the client. A panic is caught on the job’s thread and returned as text. A job that outlives its deadline cannot be stopped from outside, so the wrapper stops waiting for it and leaves the thread to end with the process, which is short-lived; the thread owns everything it uses and shares nothing with the caller, so abandoning it leaves nothing locked. The thread has the linter’s own stack size, because the main thread’s stack is set by the platform and a stack overflow cannot be caught; the size makes an overflow as unlikely as in the executable, it does not contain one.
- markdown_
linter_ output - What one in-process linter run produced:
the fixed source and the findings left,
or why it is unusable.
What:
Read what one in-process
monochromatic-lintfix run produced: the fixed source and the findings it could not fix, or the reason the run cannot be trusted. Why: The linter’s own contract (package/linter/monochromatic-lint/src/run_embedded.rs) is the executable’s--stdin --fixrun with its one-rule configuration: status 0 with the fixed source and warning findings, because the policy’s configuration gives the rule severitywarn; 1 only for error findings or a warning limit, neither of which that configuration can produce; and 2 for a run that could not finish. Anything else, and any finding that is not a warning of a selected rule with a position, means the run cannot be trusted, so the policy fails instead of passing an unchecked file. - merge_
isolation - A true merge completed in isolation while the index holds staged changes.
What:
A true
git merge <branch>completed in isolation when the index holds staged changes that make native Git refuse it: the merge is computed from objects, committed, and applied with Git’s own two-way tree update, which keeps every entry and every file the merge does not change. Why: Several sessions share one checkout. Native Git refuses every merge that creates a commit while the index differs fromHEADat all (measured indoc/planning/cli-git-merge-with-staged-index.md), although the merge may touch none of the staged paths. A fast-forward is not refused for unrelated paths, because it is a two-way update that keeps entries it does not write; a merge commit whose first parent is the current commit is exactly such an update. So the merge tree comes fromgit merge-tree --write-tree, the commit fromgit commit-tree, and the checkout moves withgit merge --ff-only <commit>under the landing lock, after a check that no changed path is staged or modified by someone else. An intent record, written before Git starts, lets recovery finish or discard a merge whose process died. - merge_
isolation_ changes - The raw differences of an isolated merge and the worktree state of each changed path. What: The raw tree differences between the two commits of an isolated merge, and the state of a worktree path against them. Why: Recovery of a merge killed while Git was updating the worktree must tell, for each path the merge changes, whether the worktree still holds the old content, already holds the new content, or holds something else that belongs to someone else and must not be overwritten.
- merge_
isolation_ journal - The durable intent record of an isolated merge. What: The durable intent record of an isolated merge: the commit it created, the branch value it expects, and the process that runs it. Why: Between the moment Git starts updating the worktree and the index for the merge commit and the moment the branch moves, a killed process leaves a worktree and an index that agree with the merge while the branch does not. The record, written and synced before Git starts, lets the next command under the landing lock decide whether the merge never started (discard it) or must be completed. Records are created once and never rewritten, like the commit transaction’s own state files.
- merge_
isolation_ message - The commit message of an isolated merge.
What:
The text of an isolated merge’s commit message and the name Git gives the merged commit in it.
Why:
The merge commit must read exactly as native Git would write it: the default subject comes from
git fmt-merge-msg, whichgit mergeitself uses, fed the description linegit mergebuilds for the commit-ish (branch 'x',tag 'x',remote-tracking branch 'x'orcommit 'x'), and any message is cleaned the way a commit that opens no editor cleans it. A commit-ish whose description cannot be built the same way is not guessed: the caller forwards the command. - merge_
isolation_ recovery - Recovery of an isolated merge whose process died. What: Recovery of an isolated merge whose process died: discard it when it never touched the worktree or the index, complete it when Git had started to update them. Why: The merge commit exists before the branch moves, and Git updates the worktree and the index of the paths the merge changes before it moves the branch. A process killed in between leaves files and index entries of the merge beside a branch that still names the old commit, which the next commit would then revert. Completing the merge from its record finishes exactly what Git had started, for exactly the paths the merge changes, and leaves every other staged entry and every unstaged change as it was. A path that holds neither its old nor its new content belongs to someone else, so recovery stops and keeps the record instead of overwriting it.
- merge_
isolation_ report - The refusal findings of an isolated merge. What: The refusals of an isolated merge, as the finding events a caller reads, each naming the paths involved and every valid way forward. Why: A merge that cannot proceed changes nothing, and the person (or the agent) who ran it must learn which files stand in the way and what to do next without reading the wrapper’s source.
- merge_
options - The
git mergecommand lines the wrapper completes in isolation, and those it leaves to native Git. What: The shape of agit mergecommand line that the wrapper can complete in isolation, or the reason it cannot. Why: The isolated merge reproduces what native Git writes only for options whose effect is known exactly: the message, the fast-forward choice, signing and the unrelated-histories switch. Any other option (a strategy,--squash,--no-commit,--ff-only,--edit, an abbreviation Git would accept) is not guessed: the command is forwarded to native Git unchanged, with the refusal it may give. - owner_
lock - Rename-published owner locks with dead-owner retirement and an unbounded wait.
What:
Rename-published owner-lock directories with process-birth ownership:
acquisition
with dead-owner retirement,
an unbounded wait while the owner lives,
and release by
rename before deletion.
Why:
The landing,
reservation,
hook,
capture and push locks all work this way in the
incumbent (
src/owner-lock/owner-lock.ts) and in its hook dispatcher, and both wrappers contend on the same directories while both are installed. A published lock always names its owner because its candidate is complete before the rename. - owner_
lock_ record - The owner record inside every owner-lock directory.
What:
The owner record inside every owner-lock directory (
owner.json, schema version 1): its strict parser, its exact encoding, and the owner-liveness rule. Why: The landing, reservation, hook, capture and push locks are directories holding this record. The incumbent and its hook dispatcher write and read the same record (src/owner-lock/owner-lock-record.ts), so both wrappers contend on one lock and each retires the other’s dead lock. - ownerless_
directory - Whether a published transaction directory without an owner record is abandoned.
What:
Whether a published transaction directory without an owner record is abandoned,
and the message for one that is not.
Why:
A power loss can persist the rename that published a transaction directory and the bytes of
owner.jsonbut not the nameowner.jsonin it. Nothing then says which process runs the transaction, so the PID and birth identity that decide every other directory are gone. The wrapper already judges an owner it cannot probe by age: an owner of another PID namespace counts as alive until its record outlivesFOREIGN_OWNER_PATIENCE(pid_namespace.rs). The same rule serves here: a directory is abandoned once nothing in it has changed for the patience and no process has a file or working directory in it. A live wrapper writes its records within minutes, and a wrapper cannot lose its ownowner.jsonname while it runs, so the age bound can fail only for a directory whose record someone removed by hand and whose wrapper then sat idle for longer than the patience. - pack_
keep - The
.keepfiles protecting a landing’s migrated pack. What: The.keepfiles that protect a landing’s migrated pack until the compare-and-swap, named by the messagecli-git <transaction-id>. Why:git index-pack --keep=<message>writes the.keepbefore the pack becomes visible, so no prune or repack drops the objects mid-landing; recovery finds a dead transaction’s.keepby its message even before a landing record names the pack (src/policy-engine/commit-landing-objects.ts). - panic_
notice - The executable’s panic hook: where an internal error happened, never its message. What: The panic hook of the executable: one line naming where an internal error happened, never its message. Why: The linked scanner catches a matcher panic and reports it as a failed scan, but Rust runs the process’s panic hook before the panic can be caught, and the default hook prints the panic’s message, which can hold bytes of the file being scanned. The scanner’s own executable installs a hook that prints nothing; this one keeps the source location, which holds no repository content, so a wrapper defect is still visible.
- path_
bytes - Exact repository path bytes as the operating system takes them, and as text for messages.
What:
Exact repository path bytes as the operating system takes them,
and as text for messages.
Why:
Git allows any bytes in a file name.
The commit path keeps a path as
Vec<u8>from Git’s listing to Git’s next argument, so a name that is not UTF-8 reaches Git unchanged; only a message for a person shows the text with replacement characters. - pid_
namespace - The PID namespace an owner record names, and the rule for owners of another one. What: The PID namespace an owner record names, and the rule for judging an owner that lives in another one. Why: A PID means something only inside its PID namespace. A wrapper in a container that probes a host owner’s PID finds no process (or an unrelated one), so it would judge a live owner dead and recover or retire what the owner still holds (issues 1781 and 1784). A record therefore names its namespace, and an owner of another namespace is judged by the age of its record instead of by the PID.
- policy_
added_ path - Whether a direct fix may change a tracked file it did not select.
What: Whether a direct fix may change a tracked file it did not select, and the
patch-conflictmessage when it may not. Why:SPEC.md(“Added paths”) admits such a file only whenHEAD, the real index, the private index and the worktree all hold the blob the correction was computed against, so the fix discards nobody’s change. The reasons and the remedy are the installed wrapper’s words (commit-transaction-added-paths.ts,assertAddablePathand its direct-fix message), checked in its order. - policy_
catalog_ specifiers - The optional catalog-specifier policy over the shared rule crate:
every dependency in a tracked
package.jsonis a catalog or workspace reference. What: The optionalmono/catalog-specifierspolicy: every dependency in a trackedpackage.jsoniscatalog:,catalog:<name>or exactlyworkspace:*, and nopackage.json5,package.yamlorpackage.ymlis tracked. Why:pnpm installaccepts a literal version without a word, so the repository checks the rule itself (doc/handover/catalog-specifier-policy.md). The rule, its findings and their wording belong to themonochromatic-catalog-specifiercrate, which the pnpm wrapper shares; this module only hands it each candidate manifest’s path and its candidate bytes, never the worktree’s, and turns its findings into the engine’s. Only the last path segment decides what a file is, and it is decided on the path’s raw bytes, so a manifest below a directory whose name is not UTF-8 is still checked. - policy_
checks - The shipped policies as checks over rule cores and repository facts. What: The shipped policies as the engine sees them: one check per policy, built from the pure rule cores and the repository facts they ask for. Why: The engine owns order, severity and stopping; the rule cores own decisions from arguments; this module joins them and fetches a repository fact only when a core asks. The content policies read the lifecycle’s candidates: they report nothing where a lifecycle has none. The Markdown policy checks the candidates it is given with the native Markdown linter.
- policy_
content - What a lifecycle offers content policies,
and its candidates once prepared.
What:
What one lifecycle offers the content policies,
and the candidates it prepared
for them,
read once and shared by every content policy of the invocation.
Why:
A forwarded command other than
git addhas no candidates, so its content policies find nothing.git addand the direct commands have candidates, which are prepared the first time a content policy reads them: an invocation whose content policies are all off or escaped starts no Git process for them. A failure to prepare is remembered, so it is reported once and never retried. - policy_
convergence - The bounded loop that repeats a pass after corrections changed candidate content. What: The bounded loop that repeats a whole policy pass after corrections changed the candidate content, until the content is stable or the loop gives up. Why: A correction by one policy can create a finding for another, so every changed state restarts the complete order. The loop must end: at most eight passes may change the content, and returning to an earlier state is a cycle. This module owns only that control flow; running policies, applying corrections and storing exact states stay behind a small interface, so the loop is testable without Git.
- policy_
dependent_ version - The
mono/dependent-version-bumpcheck over a lifecycle’s candidate state. What: Themono/dependent-version-bumpcheck: the native planner over the lifecycle’s prepared candidate state, its findings, and the corrections a direct fix applies. Why: The installed wrapper’s policy (dependent-version-bump-policy.ts) reports one finding per publishable dependent of a raised manifest, each with a full-content patch, ondirect-checkanddirect-fix; onpre-forwardit plans only for a forwardedcommit. A realgit commitruns through the commit transaction instead of being forwarded, sopre-forwardreaches this check only forgit add, where the policy finds nothing. - policy_
engine - Ordered policy execution for one lifecycle point. What: Run shipped policies in registry order for one lifecycle point and settle the outcome: which events were produced and whether the command may proceed. Why: Every lifecycle shares one execution contract: a policy runs only for the triggers it declares, at the configured severity, unless it is filtered out, escaped or off; the first error finding stops the pass unless the caller asked to keep going; and a policy or lifecycle this executable cannot evaluate is reported as unavailable, never as clean.
- policy_
events - Policy events and their JSON Lines rendering.
What:
The events one policy pass produces,
and their JSON Lines rendering.
Why:
Agents and scripts read these lines from wrapper standard error and from
git cli-git check|fixstandard output. Each event is one compact JSON object with the incumbent’s field names and field order, so existing readers parse native events unchanged. Events are numbered when rendered, so a list of events always prints with consecutive numbers in emission order. - policy_
final_ newline - The built-in final-newline policy:
canonical final LF,
preserved paths,
findings.
What:
The built-in
final-newlinepolicy: a non-empty UTF-8 text file without NUL bytes must end with exactly one LF byte. Why: The rule, its finding and its generated-output path families are the installed wrapper’s (final-newline-policy.ts,final-newline-normalize.ts), so the native wrapper reports the same files with the same words. A file whosetextGit attribute is unset (-text, whichbinaryincludes) keeps its exact bytes, which the installed wrapper could only do by path; the attribute travels with a package that moves, where a path list goes stale. The normalized bytes are also the full-content correction a fixing lifecycle applies. - policy_
forbidden_ strings - The optional forbidden-strings policy over the linked scanner,
with redacted findings.
What:
The optional
security/forbidden-stringspolicy: scan every candidate’s exact bytes and pathname through the linked scanner and report each match, redacted. Why: The installed wrapper wrote candidate bytes to temporary files, started the scanner executable and parsed its standard error. Here the same scanner is a library: rules are loaded once per invocation, only when a candidate can be scanned, and each finding carries the scanner’s masked display path and an opaque rule name, never the matched text. A scan that could not complete ends the policy with the code of its cause, never as a clean result. - policy_
markdown - The
markdown/autofixpolicy over candidates, behind its linter interface. What: The optionalmarkdown/autofixpolicy: every Markdown candidate the lifecycle offers is passed throughmonochromatic-lint --fixwith the policy’s rules; a changed source is reported, and corrected ongit cli-git fix, and each finding the fixes leave is reported as it is. Why: The installed wrapper’smarkdownLintPolicy(package/git-policy/markdown-lint/src) decides the behaviour: deleted files, symbolic links and submodules are skipped, and so are excluded paths before any linter starts and contents that are not UTF-8; candidates are linted one at a time, in candidate order; the codes and words of both findings are kept. One thing differs by design: the extension test is the linter’s own (.mdand.mdx, exact case), so no file is sent that the linter refuses. The autofix finding keeps the installed wrapper’s words, which name its former linter, so both wrappers report the same file the same way while both are in use. - policy_
pass - One complete pass:
built-in policies,
fixed transforms,
optional policies.
What:
One complete policy pass over the shipped policies:
the built-in policies,
then
the fixed argument transforms of a forwarded command,
then the optional policies.
Why:
Every lifecycle runs the same three stages in the same order,
and each stage can
end the pass.
Keeping the sequence in one place means a wrapped command and a
direct
git cli-git checkcannot drift apart in what they run or when they stop. - policy_
registry - The fixed registry of shipped policies. What: The fixed registry of policies compiled into the native wrapper. Why: Repository JSONC can only tune shipped policies; it can never name code to run.
- policy_
root_ context - The optional root-context policy:
no top-level
CONTEXT.mdenters the index. What: The optionalmono/forbidden-root-contextpolicy: aCONTEXT.mdfile at the repository’s top level must not enter the index or be checked. Why: This repository keeps no context files; agents read the source on every probe (doc/agent/domain.md). The rule and its finding are the installed wrapper’s (package/git-policy/repository/src/index.ts): only the top-level pathname counts, and removing the file is always allowed. - policy_
transforms - The fixed argument transforms of a forwarded command,
in order.
What:
The fixed argument transforms of a forwarded command,
in their fixed order:
atomic push,
commit-only,
status hints off.
Why:
These are not policies:
they have no severity and cannot be configured.
They add
--atomictogit push,-otogit commitand-c advice.statusHints=falsebeforegit status, and the commit-only transform can reject a commit. The decisions belong to the pure rule cores; this module runs them in order and fetches the two repository facts commit-only may ask for. - policy_
trigger - Lifecycle triggers and the trigger set of every shipped policy. What: The lifecycle points at which policies run, which shipped policy runs at which point, and which points this executable can serve. Why: A policy runs only for the triggers it declares. A trigger whose lifecycle is not ported must be answered as “unavailable”, never as an empty result that would read as “checked and clean”.
- post_
commit - The post-commit gate over a landed commit: its events, its blocking and the backup it allows.
What:
The post-commit policy gate over a landed commit’s own changes, and the backup that follows it.
Why:
The incumbent (
src/policy-engine/post-commit-lifecycle.ts) checks the committed bytes of every path a landed commit changed, and only a gate that passes lets the commit reach auto-push. A blocked gate keeps the commit local and names it in acommit-landedevent; the command exits2. The landed commit is an input, so the integrator can call this after any landing. - post_
landing - Post-landing completion of one landed commit: worktree copies, disposal, maintenance, then
post-commit. What: The post-landing completion of one landed commit, in the incumbent’s order: added-path worktree copies, shadow and transaction removal with capture pruning, automatic maintenance. Why: The incumbent’scommit-transaction-conclusion.tsruns these steps after a landing. Worktree copies come first, so an interruption before cleanup leaves the journal for recovery to finish them. Maintenance bounds the pack count each landing grows. A maintenance failure is logged and never fails the landed commit; a copy or disposal failure fails the command with its message, as the incumbent’s thrown error does. No hook and no program named by a configuration runs after the landing. - private_
index_ stage - Slice 6: writes one corrected file into the private commit index as a blob and an index entry.
What:
Write one corrected file into the private commit index: its bytes as a blob,
then the index entry that names that blob.
Why:
Corrections land in the private index, never in the real index or the worktree.
The incumbent patches the private index with
git apply --cached; here the policy already holds the corrected bytes, so the blob and the entry are written directly. - private_
storage - Private,
crash-safe file primitives shared by every durable record.
What:
Private,
crash-safe file primitives shared by every durable record of the wrapper:
exclusive no-follow creation with private modes,
file and directory sync,
and
no-follow reads that refuse links and non-regular files.
Why:
Lock records,
transaction journals and capture records must survive a crash in a
readable state,
must never be written through a planted link,
and must be private to
the account.
The incumbent’s
src/trust/registry-io.tsdefines these semantics; both wrappers write and read the same files. - process_
identity - Process-birth identities in the incumbent’s exact spelling.
What:
Process-birth identities:
a string that names one process for its whole life and
changes when its PID is reused,
in the incumbent’s exact spelling.
Why:
Every lock and transaction records its owner’s PID and birth identity;
an owner is
alive only while its PID names a process with the recorded identity.
Both wrappers
judge each other’s locks,
so the strings must agree byte for byte
(
src/policy-engine/commit-transaction-process-identity.ts; the decision indoc/handover/cli-git-rust-implementation.md, “Adopted without a question”). - process_
start - Process start times for lock owners,
read from
/proc,psor the Windows process table. What: The wall-clock start time of a process, with its resolution, for comparing a Git lock PID file’s owner with the lock’s change time. Why: A PID file names a live owner only when that process started no later than the lock changed; a younger process reused the PID. Linux derives the start from/proc/<pid>/statfield 22,/proc/uptimeand the current time; macOS readsps -o lstart=; Windows readsGet-ProcessStartTime(src/index-lock/process-start-time.ts). - process_
tree - The processes below one process, read before it is stopped and signalled only while they are still the ones found.
What:
The processes below one process: found before it is stopped, and signalled afterwards, each only while it is still
the process that was found.
Why:
Stopping a process does not stop what it started. A
git pushthat is sentSIGTERMorSIGKILLleaves its transport helper (git-remote-http,ssh) and the hooks it ran (git-lfs) alive, orphaned to the init process, holding their connection until their own timeout ends them (measured: a helper stayed 60 seconds, agit-lfsthat never exits stayed for good). The tree has to be read before the root dies, because a child’s link to its parent is gone once the parent has exited. A process number can be reused, so each process is identified by the start time the kernel recorded for it, and a number whose start time changed is not signalled. The list comes from/proc, so only Linux finds any; elsewhere the tree is empty and only the root is stopped. - process_
use - The processes of this host that use a directory or a file.
What:
The processes of this host that use a directory or a file:
a working directory or an open descriptor inside the directory,
or an open descriptor on the file.
Why:
Recovery may treat a directory or a lock as abandoned only when no process can still be using it.
A process that holds a path open is the one proof of use that survives a lost record,
so the scan reads it from
/procand says what it could not read instead of guessing. Only processes of the scanning account are reported as unreadable: another account’s process cannot have opened a private file this account created, and every unprivileged scan meets some of them. - push_
stacking - An explicit push that lost a race: run as a child, merge in what landed on the remote and publish the merge.
What:
An explicit
git pushthat lost a race: the commits that landed on the remote since the local base are merged in the object store, and the merge is published by the wrapper pushing it again through its own gates. Why: Several sessions push to one branch all day; a push rejected because the remote moved cannot succeed again, and every caller would fetch, merge or rebase and retry by hand while racing the others. The wrapper runs an eligible push as a child with the caller’s streams, so Git’s own output is unchanged. Only a failed push is looked at again, and only a divergence of the destination is acted on; an authentication, offline, hook or rule refusal keeps the exit status and the text Git gave. The merge is pushed through the same push planning, run inside this process (stacked_merge_push) withSTACKED_PUSH_MARKERset in the planned environment, so the policies that gate any push read the integrated result and the marker stops that push from stacking again. The wrapper never starts itself for this. A push that carries the Git LFS upload also runs here, as a child, so this parent can explain a failed upload. - random_
id - Random identifiers in the layout of the incumbent’s
randomUUID. What: Random identifiers in the layout of the incumbent’srandomUUID: lock tokens, transaction IDs and stale-name suffixes. Why: A lock token must be unguessable and unique per acquisition, and a transaction ID names a registry directory that the incumbent accepts only in the canonical layout (isTransactionIdinsrc/policy-engine/commit-transaction-registry.ts). - real_
git - PATH resolution of the real Git executable.
What:
Find the real Git executable on PATH without ever selecting this wrapper.
Why:
The wrapper is installed as
gitahead of real Git; forwarding needs the first usable candidate that is not a cli-git wrapper. - real_
git_ candidate - Wrapper self-exclusion for one PATH candidate.
What:
Decide whether one PATH candidate is real Git,
a cli-git wrapper,
or unusable.
Why:
The wrapper shadows
giton PATH and must never select itself, a copy of itself, or a script that starts the TypeScript wrapper. - real_
index_ lock - The real index.lock a landing creates,
identifies and releases only while it is the same file.
What:
The real
index.locka landing holds, created exclusively, identified by device, inode and filesystem, announced throughindex~pid.lock, and released only while it is still the file that was created. Why: The landing’s index install depends on the exact lock file it created; a replaced lock must never be deleted, and a lock this process did not create must never be released (src/policy-engine/commit-landing-index-lock.ts). - recovery_
completion - Deciding whether a landing landed,
and installing its recorded post-index.
What:
Deciding whether a dead transaction’s landing attempt landed,
and installing or
recognizing its recorded post-index.
Why:
Before
ref-updated.jsonthe only proof of a landing is the nonce entry in the target’s reflog; missing or contradictory evidence fails closed. Artifacts are pinned under stable names by inode before use (src/policy-engine/commit-transaction-recovery-completion.ts). - recovery_
contradiction - The refusal to discard a transaction whose branch moved but whose records say otherwise.
What:
The check that a dead transaction without a usable landing record did not move its branch.
Why:
Recovery treats a transaction with no
landing-<n>.json(or nopreparing.json) as one that stopped before it landed, and removes it. The journal writespreparing.jsonfirst,landing-<n>.jsonbefore the branch moves andref-updated.jsonafter it, each synced before the next, so a directory that holds a later record without an earlier one, or a branch reflog entry of the transaction without a landing record, is a record set that contradicts itself. Removing it would leave the branch ahead of an index nobody reconciled and report success. Recovery cannot repair that state (the landing record held the post-index identity, its digest and the lock identity), so it fails closed, names the evidence and leaves the directory. Anindex-installedmarker means the install finished, so nothing is left to reconcile and the directory goes. - recovery_
dead_ owner_ locks - Where a dead owner’s real index lock is found, and the release of the locks its surviving records prove.
What:
Where a dead owner’s real index lock is found, and the release of what the owner held when the records that prove
the lock ours are partly lost.
Why:
Recovery releases a dead owner’s lock through its
index-lock-<n>.jsonrecord, and reads the real index path frompreparing.json. A power loss can take the name of either record, so this module finds the worktree’s own index withoutpreparing.json, recognizes the PID file that announces the dead owner, and releases the locks the surviving records or the PID file prove (recovery_unrecorded_lock.rsholds the proofs). - recovery_
error - The fail-closed failure of reading or recovering durable transaction state.
What:
The fail-closed failure of reading or recovering durable transaction state.
Why:
Malformed,
unsafe or contradictory state is never guessed at or deleted:
the command
stops with exit status 2 and a message naming the path,
and the state stays for
inspection (
CommitTransactionRecoveryErrorin the incumbent; implementation plan, “Commit transactions and recovery”). - recovery_
evidence - Durable evidence a dead transaction left:
attempt records,
owned locks and Git’s PID file.
What:
Durable evidence a dead transaction left:
its numbered attempt records,
the real
index.lockfiles those records prove it created, and Git’s PID file naming it. Why: Recovery releases exactly what the dead owner held and nothing else (src/policy-engine/commit-transaction-recovery-evidence.ts). - recovery_
files - No-follow file operations recovery and landing share.
What:
No-follow file operations recovery and landing share:
existence probes,
exact reads,
byte comparison,
owner-preserving hard links,
proof that a real
index.lockis the exact file a transaction created, and installing an index through such a lock. Why: Recovery removes or installs through a realindex.lockonly when the journal proves the lock is the transaction’s own, by device, inode and filesystem identity, never by path; the incumbent’scommit-transaction-recovery-files.ts,-validation.tsandcommit-transaction-install-link.tsdefine these steps. - recovery_
inspect - Owner inspection of registry entries and the oldest-first order.
What:
Owner inspection of one transaction registry entry,
and the oldest-first recovery order.
Why:
Live owners are skipped,
dead owners recovered,
a published directory with a malformed owner record fails closed,
and one without an owner record is recovered once abandoned (
ownerless_directory.rs) and fails closed before; recovery runs oldest first because a later transaction was prepared against the state an earlier one left (src/policy-engine/commit-transaction-recovery-inspect.ts,commit-landing-lock.ts). - recovery_
landing - Recovery of one dead-owner transaction.
What:
Recovery of one dead-owner transaction from its schema-version-2 records.
Why:
Without a landing record the real index and refs were never touched:
recovery releases
the
index.lockthe transaction provably created and removes its.keepfiles, shadow and directory. With a landing record (the caller holds the landing lock) it decides fromref-updated.jsonor the reflog nonce whether the commit landed, then discards the attempt or completes it: index, conclusion cleanup, added-path copies. Missing evidence fails closed and keeps the directory (src/policy-engine/commit-transaction-recovery-landing.ts). - recovery_
manual_ steps - The steps that finish a retained transaction by hand. What: The sentence that tells a person how to finish a retained transaction by hand. Why: Recovery fails closed on a transaction it cannot complete and keeps its directory; every guarded command then stops with exit status 2 until that directory is removed. The steps are the same for every such failure, so every message says them the same way: check that nothing is running, remove the leftovers, and bring the index to the branch.
- recovery_
record_ failure - The failure of a journal record that cannot be read, with its path and ways out named. What: The recovery failure for a journal record that cannot be read, with the file, the transaction and the ways out named. Why: A strict parser knows only the bytes it was given, so its message names the kind of record and the field. The person who sees it needs the path of the file, the transaction directory, whether the file can be removed or restored, and what the wrapper does next; every guarded command stops with exit status 2 until then. The caller that read the file knows its path, so it wraps the parser’s failure here.
- recovery_
reflog - The reflog search that proves which commit an interrupted transaction landed.
What:
The reflog search that proves which commit an interrupted transaction landed.
Why:
Before
ref-updated.jsonexists, a landing counts as landed only when the target’s reflog holds the transaction’s nonce entry; the search covers the whole reflog because later landings push the entry down (src/policy-engine/commit-transaction-recovery-reflog.ts). - recovery_
scan - Recovery across every transaction directory of one worktree.
What:
Startup recovery across every per-transaction directory of one worktree.
Why:
Live owners are skipped;
dead owners are recovered oldest first;
dead transactions that
entered the landing critical section are recovered only while holding the landing lock,
so recovery never races a live lander (
src/policy-engine/commit-transaction-recovery-scan.ts,commit-landing-lock.ts). - recovery_
snapshot - The check that an index snapshot still holds the bytes the landing wrote. What: The check that an index snapshot still holds the bytes the landing wrote, made before recovery trusts it. Why: Neither snapshot is synced when the landing writes it, while the landing record that names them is. A power loss can persist the record and the moved branch and leave a snapshot empty, cut short or filled with bytes that never reached the disk. Installing such a post-index leaves a real index every later command rejects, and trusting such a pre-landing snapshot would compare the real index with the wrong bytes. The check recomputes the length and hash the landing recorded; a record without them (written before they existed) is trusted as before.
- recovery_
unrecorded_ lock - The release of a real index lock that a dead wrapper’s PID file announces without a lock record.
What:
The release of a real
index.lockthat a dead wrapper’s PID file announces although the record that proves the lock ours (index-lock-<n>.json) is gone. Why: Recovery releases a dead owner’s lock only through its journal record, which names the exact file the owner created. A power loss that keeps the lock and its PID file but loses the record’s name leaves a lock nothing proves ours, and every later commit then refuses it as an unproven owner. The surviving facts bind the lock to the dead wrapper without the record: the lock is empty, it was created after the transaction was published and not after the PID file was written, the PID file names the dead owner, no process of that number exists in the owner’s PID namespace, and no process whose descriptors can be read holds the lock open. A lock another process created later is dated after the PID file, and a process that holds it open is found by the scan, so each refuses the release. Callers hold the landing lock, which keeps every other wrapper from taking the lock meanwhile. - ref_
files - Files-backend ref reads that answer without a Git process or decline.
What:
Reads of the files ref storage that answer a question without a Git process,
and refuse to answer anything they cannot prove equal to Git’s answer.
Why:
git symbolic-ref --quiet HEADonly reads the worktree’sHEADfile and, for a branch, checks that the branch is not itself a symbolic ref. A strict reader of the plain spellings saves a process on every commit, and every other spelling (a different ref name shape, a link, a symbolic chain, reftable storage) returnsNone, which sends the caller to Git. - replay_
capture_ history - Replay: capture-order history and the per-path decisions from the transaction’s and landed records (
replay_capture_history). What: The capture-order inputs of one replay: the first-parent commits landed since the preparation base with their changed paths, and the decisions for the shared paths read fromcaptured.jsonand the landed-capture records. Why: Two Git processes list the history whatever its length; each landed commit’s record says which worktree captured which path and when, which is what the decision compares. - replay_
capture_ order - Replay: capture order over the landed first-parent history and the per-path decision (
replay_capture_order). What: Capture order for one replay: the landed first-parent changes, the per-path decision between the prepared and the landed captures, and the parser of thegit diff-tree --stdinoutput that lists the changes. Why: When the prepared commit and every landed commit that changed a path captured it from the same worktree, the later capture’s bytes land, which is what sequential native commits record from the shared disk. Every other path keeps subsumption and then the three-way merge. - replay_
commit - Replay: one replay of a prepared commit onto a moved target, the integrator’s replay result (
replay_commit). What: One replay of a prepared commit onto a moved target, as the landing loop runs it outside both locks: shared paths, capture order, subsumption, the merge, and either the replayed commit or the conflict. Why: This is the replay result the integrator consumes. Revalidation after a clean merge (policies,pre-commit) and the events are the integrator’s; this function returns the data forcommit-replayed,replay-headers-dropped, andconcurrent-commit/replay-conflict, and the replayed tree the revalidation reads. - replay_
containment - Replay: the one-sided extension of subsumption over zero-context hunks (
replay_containment). What: The context-tolerant half of replay’s subsumption: whether each prepared change is the landed change extended by the prepared commit’s own edit on one side. Why: Strict reverse application rejects a prepared file holding the landed edit plus an own edit next to it, because that own edit sits in the landed hunk’s context. Reading both changes as zero-context hunks in base coordinates accepts exactly the one-sided extensions and still rejects own edits on both sides and modify/delete conflicts. - replay_
merge - Replay: the merge tree, the first conflicting landed commit, and the replayed commit rebuild (
replay_merge). What: The merge and rebuild steps of one replay: the replay merge base, the tree fromgit merge-tree --write-tree, the first landed commit that touched a conflicting path, and the replayed commit written from the prepared one. Why: Replay runs outside both landing locks in the shadow repository. An unsigned prepared commit is rebuilt by rewriting its raw object, which keeps every byte; a signed one is re-signed withgit commit-tree -S, because no Git primitive re-signs a raw object. - replay_
object - Replay: raw commit objects as bytes, the unsigned rewrite and signed-rebuild inputs (
replay_object). What: Raw commit objects as exact bytes: parsing, the unsigned replay rewrite, and the inputs a signed replay passes togit commit-tree. Why: Replay keeps every byte of the prepared commit except itstreeandparentlines, so headers and message stay bytes; a non-UTF-8encodingcommit round-trips unchanged. - replay_
patch - Replay: the unified-diff parser of subsumption (
replay_patch). What: The unified-diff parser that replay’s subsumption reads:git diff-tree -poutput over single-level trees named by decimal index, split into hunks per entry name. Why: Subsumption decides from hunk lines and their markers; the parser keeps every byte exact, so a hunk line round-trips to the blob bytes it came from. - replay_
reservation - Replay: the starvation reservation of the next landing slot (
replay_reservation). What: The starvation reservation of the next landing slot: a transaction that lostlanding.reserveAfterLostRacesraces writesreservation-request, takes the reservation lockreservation.lockin the registry when it is granted, and every other transaction waits before it takes the landing lock. Why: Without it a commit that keeps losing the race can starve behind a stream of landings. Grant order is the oldest live requester, so the transaction that waited longest goes first. - replay_
revalidation - Replay: the replayed index, the shadow HEAD move, and the pre-commit re-run decision of revalidation (
replay_revalidation). What: The parts of revalidation after a replay that need no policy: the replayed index and the shadowHEADmove the policies see. Why: Policies belong to the integrator, which owns the policy engine; these three steps are pure Git work the integrator calls before it. - replay_
reverse_ apply - Replay: the exact strict reverse check of subsumption (
replay_reverse_apply). What: The exact emulation ofgit apply --reverse --checkfor one text file: the subsumption test that asks whether a landed change already sits in the prepared bytes. Why: Git’sapply.cmatching (apply_one_fragment,find_pos,match_fragment) with its defaults: no context reduction, no whitespace fixing, no overlapping hunks, and anchoring at the file’s start and end. Replay must give the same answer as Git for the same bytes. - replay_
shadow - Replay: the shadow-repository Git runner that every replay step uses (
replay_shadow). What: Running real Git against the shadow repository for replay: the shadow selection, an optional private index, bytes on standard input, and the caller’s kept global options when a signed rebuild needs them. Why: Every replay step runs ingit --git-dir=<shadow>so its objects stay in the shadow store until the next migration; one runner keeps the selection in one place. - replay_
shared_ paths - Replay: shared paths of the landed history and the prepared commit, with their three entries (
replay_shared_paths). What: The paths both the prepared commit and the landed history changed since the preparation base, with the three tree entries subsumption compares. Why: Subsumption decides per shared path; the listing is twogit diff-treeruns whatever the history length, and paths stay Latin-1 decoded so every byte survives and encodes back exactly. - replay_
subsumption - Replay: subsumption rules and the synthetic merge base (
replay_subsumption). What: Subsumption before replay’s three-way merge: the per-path rules, and the synthetic merge base that makesgit merge-treetake the prepared bytes for every subsumed path and the landed bytes for every path capture order drops. Why: A path whose prepared bytes already contain the landed change keeps the prepared entry, matching what native sequential commits produce in a shared worktree. Setting the synthetic base to the landed content means the merge sees no landed change there. - replay_
subsumption_ text - Replay: the text half of subsumption over index-named blobs and hunks (
replay_subsumption_text). What: The text half of replay’s subsumption: which regular-file paths hold prepared bytes that already contain the landed change. Why: A path is subsumed when the landed change applies in reverse to the prepared bytes with full context, or when the prepared change of every landed region is the landed change extended on one side. Hunks come from index-named single-level trees, so no patch header carries a user path, and binary detection is Git’s content rule (a NUL byte in the first 8000 bytes of any of the three blobs). - repo_
read_ adapter - In-process repository reads: the layout turned into the shapes the wrapper’s existing queries answer with.
What:
The shapes the wrapper’s existing queries answer with, built from an in-process
RepoLayout. Why: Each caller keeps its own result type; the adapter is the one place that says how a layout becomes aRepositoryLocation, aLandingLocationor a sequencer state, so a caller tries the layout first and asks Git only when the layout declines. - repo_
read_ config_ list - In-process repository reads: the merged configuration listing with includes and environment entries.
What:
The merged configuration listing of
git config --list -z --show-scope, built in process from the system, global and repository files, theirinclude.pathfiles, and the environment’sGIT_CONFIG_COUNTentries. Why: The wrapper reads this listing once per commit and asks several single-key questions Git answers from the same files. The reader declines conditional includes (whose condition Git evaluates), a worktree-specific file, command-line-centries (GIT_CONFIG_PARAMETERS), a file it cannot parse and an environment entry it cannot canonicalize. - repo_
read_ config_ scan - In-process repository reads: proof that no configuration scope sets
core.bareorcore.worktree. What: The proof, from files alone, that no configuration scope Git reads for repository discovery setscore.bareorcore.worktree. Why:git rev-parseclassifies a repository from the discovered directory plus those two keys of any scope; the in-process location reader may only answer when none of the files it can see mentions either key, so the check follows everyincludeandincludeIfpath without evaluating its condition (a conditional include that mentions a key is enough to leave the question to Git). - repo_
read_ config_ text - In-process repository reads: the plain-text configuration reader that gives up on any spelling it cannot prove.
What:
A reader of Git configuration file text that gives the entries in file order, and gives up on any spelling it
cannot prove it reads as Git reads it.
Why:
The in-process repository reads (location, remotes, upstream) need a few configuration values;
Git answers a wrong or surprising spelling with its own error,
so every spelling outside the plain grammar returns
Noneand the caller asks Git. - repo_
read_ config_ typed - In-process repository reads: proof that every configuration value Git types at startup is one Git accepts silently.
What:
The proof that every configuration value Git types at startup, in every scope the readers look at, is a value
Git accepts without a message.
Why:
Git reads many keys for every command (
core.fileMode,core.compression,advice.*,diff.context, and so on) and dies (fatal: bad boolean config value 'maybe' for 'core.filemode'), or warns, when a value does not fit the key’s type. The readers never look at most of those keys, so without this proof they would answer where Git fails. The proof accepts only spellings it can show Git accepts (a conservative subset) and leaves every other spelling of a typed key to Git, which then words its own failure. The key table was built from the keys real Git 2.56.0 rejected or warned about when set to malformed values (doc/planning/cli-git-in-process-reads.md, “Typed configuration values”), andevery_documented_key_with_a_malformed_value_is_declined_or_equals_gitkeeps it honest against the Git that runs the tests. - repo_
read_ diff - In-process repository reads: the difference between two sorted entry lists and the output spellings.
What:
The difference between two sorted lists of tree-like entries (a flattened tree, an index), in Git’s raw and
name-only spellings, and the pathspec forms and filters the wrapper’s own queries use.
Why:
diff-index --cached,diff --cachedanddiff-treeprint exactly these lines for the plain cases; every case where Git’s output depends on more than the two lists (a file that became a directory, a gitlink, a possible rename) returnsNone, so Git answers it. - repo_
read_ hook - In-process repository reads: the two runner entry points that try the answers before starting Git.
What:
The two places where the wrapper’s Git runners try the in-process answers first.
Why:
run_metadata_git(captured queries) andrun_git(transaction requests) are the choke points every read-only Git start goes through, so one call in each gives every caller the saving and keeps the fallback to Git in one place. SettingCLI_GIT_IN_PROCESS_READS=offin the wrapper’s environment turns the answers off, for a measurement that compares both and for diagnosing a difference. - repo_
read_ index - In-process repository reads: the entries of an index file, versions 2 to 4.
What:
The entries of an index file read in process, for versions 2, 3 and 4.
Why:
ls-files --stage,ls-files --unmergedand the cached diffs only need each entry’s path, mode, object name and stage. The reader returnsNonefor a split index (link), a sparse index (sdiror a directory entry), an unknown required extension, unsorted entries or a size that does not add up, so Git answers those. It does not verify the trailing checksum; Git does, and a torn index cannot occur because writers rename a finished file. - repo_
read_ layout - In-process repository reads: the location, Git paths and object format found from the file system.
What:
The repository location that
git rev-parse --path-format=absolute --is-bare-repository --git-dir --git-common-dir --show-toplevel --show-prefixprints, found from the file system without a Git process. Why: Every wrapped command asks for it, and it is the only extra Git start of most forwarded commands. The reader walks up from the directory looking for.git, checks what Git checks for the plain layouts (a validHEAD, the objects and refs directories, files ref storage, a known object format, the owner of every path, nocore.bareorcore.worktreein any scope), and returnsNonefor everything else, which sends the caller to Git: a bare repository,GIT_*variables, a symbolic link, another file system, reftable, an unknown extension, a submodule that setscore.worktree. - repo_
read_ object - In-process repository reads: object kinds, names and the zlib step shared by loose objects and packs. What: The shapes of a Git object read in process, object names as bytes and hexadecimal, and the zlib step. Why: Loose objects and pack entries are zlib streams; every reader of this family shares one inflate call (a pure Rust decoder) and one definition of an object name, so a size or format mismatch is refused in one place.
- repo_
read_ odb - In-process repository reads: the object database of a repository, its packs and alternates.
What:
The object database of one repository read in process: loose objects, packs and alternates.
Why:
A lookup that finds the object answers exactly what
git cat-filewould print for it; a lookup that does not (missing, corrupt, too large, replaced by arefs/replaceref, in a shallow or grafted history) returnsNone, and the caller asks Git, which words the failure or fetches a promised object itself. - repo_
read_ pack - In-process repository reads: one pack, its index lookup and delta resolution.
What:
A reader of one Git pack: the version 2 index lookup by object name and the entry read, with
OFS_DELTAandREF_DELTAresolution. Why: Most objects of a real repository are packed. The reader opens the two files, seeks for the few bytes a lookup needs, and returnsNonefor anything it does not fully understand (a version 1 index, an inconsistent size, a delta chain deeper than the limit, a bad delta), which sends the caller to Git. - repo_
read_ query - In-process repository reads: answers to the exact read-only command lines the wrapper issues.
What:
Answers, without a Git process, to the exact read-only Git command lines the wrapper itself issues:
ls-files --stage,ls-files --unmerged,diff-index --cached,diff --cached --name-only,diff-tree --root -r -mandshow --no-patch --format=%s. Why: Each is a start of Git for a fact two in-process readers (the index, the object database) can produce byte for byte. The reader takes the same argument list the caller would give Git and returns Git’s standard output and exit code, orNonefor any command line, repository or configuration it does not fully understand, so a caller tries it first and starts Git onNone. - repo_
read_ query_ refs - In-process repository reads: answers for the reference and configuration command lines.
What:
In-process answers for the reference and configuration command lines the wrapper issues:
config --list -z --show-scope,remote get-url <name>,rev-parse --verify --quiet <ref>^{commit},for-each-ref --format=%(refname)%00%(objectname)%00%(symref)andrev-parse --abbrev-ref --symbolic-full-name @{upstream}. Why: Each reads only the configuration files,HEAD, the loose and packed refs and at most a few objects; every case where Git’s output depends on more (a linked worktree, a conditional include, a rewritten URL, an ambiguous short name, a broken ref) isNone, which starts Git. - repo_
read_ refs - In-process repository reads: the files ref storage, one ref by name, a symbolic chain and the list of all refs.
What:
The files ref storage read in process: one ref by exact name, the symbolic chain behind it, and the list of every ref.
Why:
A loose file wins over
packed-refs; a symbolic file names another ref; both are plain text. Every spelling the reader is not sure of (a link, a name outside a plain alphabet, a damaged value, a linked worktree’s split of per-worktree and shared refs) isUnsure, which sends the caller to Git. - repo_
read_ tree - In-process repository reads: trees, flattened trees and commit fields.
What:
Trees and commits read in process: the entries of a tree, the flat list of a whole tree, and the header fields
and subject of a commit.
Why:
The diff and listing readers compare a commit’s tree with an index or another tree path by path, and
showprints a commit’s subject; both need only these parses, and a commit with a header the parser does not know (a different message encoding, a signature it must not interpret) returnsNoneso Git answers. - repository_
facts - Lazily fetched repository facts behind one interface, with the real-Git provider. What: The repository facts policies and fixed transforms need, behind one small interface, with the provider that asks real Git. Why: The rule cores decide from arguments alone and name the fact they still need. This module fetches those facts lazily and at most once: an invocation whose rules need nothing starts no Git process, and several rules that need the repository’s location share one query. Tests drive the rules with a scripted provider.
- repository_
location - The one query for repository identity,
top level and the path below it.
What:
Ask real Git,
in one query,
which repository an invocation selects and where
inside its worktree the command runs.
Why:
The configuration root,
the require-root policy and the worktree policies all need
answers Git itself computes from
-C,--git-dir,--work-treeand its environment. Onegit rev-parseprints all of them, so an invocation starts one Git process for these facts instead of one per fact, and the wrapper never re-implements Git’s repository discovery or its-Cchaining. - rule_
add_ explicit - Pure decision of the add-explicit policy.
What:
Pure decision of the add-explicit policy:
git addnames the paths it stages. Why:git add .,git add -Aandgit add -usweep up every changed path, including ones the caller never looked at, so one commit stops describing one change. The policy rejects those forms. This file decides from the arguments alone; it reads no file and starts no process. - rule_
argument_ rewrite - The shared result and builder of transforms that add tokens to a command line. What: The shared result and builder of fixed transforms that add tokens to a command line. Why: A transform either leaves the caller’s arguments alone or returns a new owned list; saying which lets the caller keep the original vector without comparing contents.
- rule_
atomic_ push - Pure decision of the atomic-push transform.
What:
The pure decision of the atomic-push transform:
insert
--atomicaftergit pushunless the caller already chose. Why: An atomic push updates every ref on the remote or none, which prevents partially applied pushes. - rule_
branch_ worktree - Pure decision of the branch-worktree-only policy and its one-query remote guess.
What:
Pure decision of the branch-worktree-only policy:
a new branch starts in its own
linked worktree,
not in the current one.
Why:
git branch <name>,git checkout -b,git switch -cand Git’s guess of a remote branch all create a branch in the current worktree. The policy rejects them and points atgit worktree add -b. This file decides from the arguments alone, names the one repository query the guess needs, and reads that query’s answer. It starts no process. - rule_
commit_ index - Pure core of the index-against-
HEADcheck. What: The pure core of the index-against-HEADcheck used by the commit-only transform. Why: The caller runs real Git; this module only says which command to run and what its exit status means, so the decision is testable without a process. - rule_
commit_ only - Pure decision of the commit-only transform.
What:
The pure decision of the commit-only transform:
insert
-oaftergit commit, or say why not. Why: Every commit must name the paths it includes. Two decisions need a repository fact (sequencer state, index againstHEAD); this module asks for the fact by returning aNeeds...variant and the caller answers through aresolve_...function, so no process or file is touched here. - rule_
commit_ only_ message - User-facing diagnostics of the commit-only transform. What: The user-facing diagnostics of the commit-only transform. Why: Each message names the rejected form and every valid way forward; keeping the text apart from the decision keeps both files readable.
- rule_
commit_ sequencer - Pure core of the merge, cherry-pick and revert conclusion check. What: The pure core of the “is a merge, cherry-pick or revert awaiting its commit” check. Why: The caller runs real Git and probes the filesystem; this module says which query to run, reads its output exactly, and decides from three presence facts.
- rule_
linked_ worktree - Pure decision of the linked-worktree-only policy.
What:
Pure decision of the linked-worktree-only policy:
commands that can discard uncommitted state never run in the main worktree, and run in a linked worktree
only while it is clean.
Why:
git stashthat changes state, a deletinggit clean, agit resetthat is not--soft,git restore,git checkoutandgit switchforms that overwrite files, a forcedrm,mv, orcheckout-index,read-tree --resetand an--abortcan throw away work, and the loss leaves no record. This file decides from the arguments alone whether the command is guarded and when, and from the worktree kind and dirtiness the caller measured whether to reject it. It starts no process. - rule_
linked_ worktree_ messages - The rejection texts of the linked-worktree-only policy.
What:
The rejection texts of the linked-worktree policy: outside any worktree, in the main worktree, and in a
worktree that holds uncommitted state.
Why:
Each text names the command, says what it could destroy, guesses what the person was trying to do, and names a
sanctioned way to do that without losing work; the deliberate way to discard anyway comes last. Every
alternative named here is run through the built executable by
binary_discard_tests.rs, and the README lists each form with the same alternatives. - rule_
require_ root - Pure decision of the require-root policy. What: Pure decision of the require-root policy: commands run from the repository root. Why: Running Git from a subdirectory makes pathspecs mean something else. The policy rejects that, except for commands that need no repository. This file decides from the argument list alone which commands are exempt, and from two paths the caller measured whether the directory is the root. It reads no file and starts no process.
- rule_
status_ hints - Pure decision of the status-hints transform.
What:
The pure decision of the status-hints transform:
insert
-c advice.statusHints=falsebeforegit statusunless the caller set that key. Why: Git’s stock status hints suggestgit add <file>...andgit commit -a, forms the wrapper’s policies reject; the wrapper prints accurate guidance instead. - rule_
worktree_ dirty - The query and reading of “does this worktree hold uncommitted state?”. What: The question “does this worktree hold uncommitted state?” as one Git query and its reading. Why: A command that can discard uncommitted state is judged by whether any exists: a clean linked worktree has nothing to lose, a dirty one does. Tracked changes (staged or not) always count; untracked files count only for the commands that delete them.
- scanner_
adapter - The linked forbidden-strings scanner: one load per invocation, typed redacted findings. What: The repository’s forbidden-strings scanner, linked in and loaded once per invocation. Why: The TypeScript wrapper wrote each candidate to a temporary file, started the scanner as a child process and parsed its standard error. Here candidate bytes go straight to the scanner library, and its own typed findings come back. Nothing is re-derived: rule loading, the rule cache, matching, redaction and the fail-closed findings all stay the scanner’s.
- scanner_
failure_ code - Which engine failure code each candidate and scanner failure carries.
What:
Which engine failure code each failure of the candidate and scanner layers carries.
Why:
An engine failure names its cause.
content-unavailablemeans candidate bytes or a repository fact could not be read;policy-incompletemeans the policy’s own machinery failed. Deciding the code here, beside the failures, makes every policy that reads candidates or scans them report the same code for the same cause. - scanner_
run - One scan pass over a candidate version’s exact bytes. What: Scan every eligible candidate of one version through the loaded scanner. Why: This is the whole forbidden-strings check of one policy pass: candidate bytes come from the candidate store (staged or committed, never the worktree), go to the scanner in memory, and the scanner’s own results come back. No temporary file is written and no process is started per file.
- scanner_
selection - Rules-file precedence and which candidates the scanner is given.
What:
Which rules files the scanner loads,
and which candidates it is given.
Why:
The scanner library filters nothing;
the standalone scanner and the TypeScript
policy applied these choices around it.
They are restated here once,
as pure
functions:
the rules-file precedence (the configured
rulesFileentries, then theFORBIDDEN_STRINGS_RULESlist, elseforbidden-strings.local.txtin the repository root) and the paths that hold the rules themselves, which would otherwise match their own content. - scratch_
location - Where short-lived private files live:
the per-user scratch directory and the repository scratch directory,
never the system temporary directory.
What:
Where the wrapper keeps short-lived private files:
the per-user scratch directory (
<state>/cli-git/scratch) for work that has no repository in hand, and the repository scratch directory (<common-dir>/cli-git-scratch) for work inside one. Both are checked as private (a real directory of this user, mode0700) and swept of entries older than a day each time they are prepared. Why: The system temporary directory is never used (issue 1809, repo rule XTD): which device backs it is unknowable (a small tmpfs, a per-user quota, a slow disk), and a full one broke every session. A file left by a killed process must not accumulate without bound, so every preparation of a directory also collects what is stale in it.doc/planning/cli-git-scratch-locations.mdrecords the choice per site. - sequencer_
marker - Proof, without a Git process, that a sequencer marker names nothing on the files ref storage.
What:
Proof, without a Git process, that a sequencer marker such as
MERGE_HEADnames nothing. Why: A commit asksgit rev-parse --verify --quiet <marker>for each of three markers, and in almost every commit none exists. On the files ref storage that question reads a fixed list of places, so when every one of them is empty the answer is “absent” without a process; any entry in any place, of any kind or content, says “maybe present” and the caller asks Git, which alone decides whether the entry resolves (a dangling marker still counts as a conclusion). - service_
amend - What an amend takes from the commit it replaces: parents, message and author.
What:
The facts of the commit an amend replaces, read once from the repository: its parents, its first parent’s tree,
its author and its message, and the author line an amend makes of them.
Why:
An amend keeps the parents of the commit it replaces, keeps its message when no message option is given and keeps
its author unless the command line says otherwise. All of that is arithmetic on the replaced commit’s own record,
so the wrapper embeds it; each rule is compared field by field with native
git commit --amendinbinary_service_amend_tests.rs. A commit with a header the amend would have to carry (another encoding, a merge tag) returns nothing and native Git handles the line. - service_
client - The wrapper’s side of a commit through the commit service.
What:
The wrapper’s side of a commit through the resident service: connect, wait honestly while the operating system
starts the service, send the request, show the queue position, survive a service that dies mid-request, and turn
every failure into the plain text of
service_messages.rs. Why: A failure the system can resolve is resolved here without the user: a service that is starting is waited for, one that crashed is reconnected to with the same request identity, one that is older than this wrapper is asked to retire and is reached again once the manager has started the new one. Only what cannot be resolved ends the command. - service_
commit - The commit command’s path through the commit service.
What:
The commit command’s path through the resident service: prepare the commit in this process (private index,
policies), send the prepared change to the service, and report what it landed.
Why:
The service is the only writer, so a commit takes no lock and polls nothing: preparation and policies run here,
outside the service’s serialized step, and the service moves the branch once for a whole batch of commits
(
doc/planning/cli-git-resident-service.md, “Where preparation and policies run”). It covers the commit linesservice_commit_args.rsaccepts (message and identity options, sign-off, trailers, a reused or fixup message,-a, an index commit, and--amend); any other command line, a commit in a merge, on an unborn branch or on a detached head, a policy that rewrites a file, and anything with nothing to commit returnNoneso the transaction handles them as before. Which route a commit takes is decided byservice_route.rs. - service_
commit_ args - Which commit command lines the commit service covers, and the message they stand for.
What:
Which
git commitcommand lines the resident service lands, and the message such a line stands for. Why: The service lands the scoped commit an agent or a person types most (git commit -m <message> -- <paths>), with the options that only shape the message or the identity:-F <file>,--author,--date,--trailer,--cleanup,--allow-empty-message,-s,--amendand--reset-author. Every other option (editor, signing, templates) still goes through the transaction until the service covers it, so this module answers “is this line one the service covers” before anything is prepared, and builds the message with the clean-up Git applies when no editor opens. - service_
fault - Test-only fault points of the commit service.
What:
Test-only fault points of the resident service:
CLI_GIT_TEST_ONLY_SERVICE_FAULT=<point>ends the service withSIGKILLthe moment it reaches the named point of a batch. Why: The kill tests need a deterministic instant, as the transaction phase markers give the wrapper (transaction_phase.rs). The marker is compiled into the release executable, and only the explicit variable arms it. - service_
handoff - The wrapper’s side of after-commit work: records, wake and notices.
What:
The wrapper’s side of after-commit work: leave the push and upkeep records for a commit the service landed, wake
the service, and show the notices that earlier work left.
Why:
The commit returns as soon as it is saved. The push (which can take as long as the network does) and the upkeep run
in the service afterwards; a push that fails is reported at the person’s next commit through the service
(
doc/planning/cli-git-resident-service.md, “After-commit work”). The wrapper starts no program and waits for no one: it writes whole files and sends one message. - service_
journal - The commit service’s journal and its restart decisions.
What:
The resident service’s journal: one file per batch of commits, with the one durable write the batch needs, and the
decision a restart makes about a batch it finds open.
Why:
The branch moves only after the intent is on disk, so after any kill the journal and the branch say whether the
batch landed; a restart finishes the one step that may be missing (the index update) or drops the intent, and a
request that landed is remembered by its identity so a retry is answered, not landed twice
(
doc/planning/cli-git-resident-service.md, “Durability and crash recovery”). - service_
land - The commit service’s serialized step.
What:
The service’s serialized step: land a batch of prepared commits on a branch, and finish the batches a killed
service left open.
Why:
Everything that must happen one commit at a time happens here, in a fixed order, with one durable write: the objects
of the whole batch are written by one
fast-import, the intent is journaled, the branch moves by one compare-and-swap, the index entries of the named paths are updated, and the intent is marked done (doc/planning/cli-git-resident-service.md, “The serialized step, group commit, FIFO and backpressure”). - service_
message - The message and identities of a service commit, made as native
git commitmakes them. What: The message and the identities of a commit the service lands, made from the command line the way nativegit commitmakes them when no editor opens. Why: Everything here is text arithmetic that Git itself defines (clean-up, sign-off, trailers, the subject of a fixup, the message and authorship of a reused commit), so the wrapper embeds it instead of startinggit commit; each rule is compared field by field with the native commit inbinary_service_options_tests.rs. Whatever cannot be made without guessing returns nothing and nativegit commithandles the line. - service_
messages - What the user is told when the commit service cannot land a commit.
What:
Everything the wrapper tells the user when it cannot get a commit landed by the resident service, and the notices
it prints while it waits.
Why:
A failure the system can resolve is resolved without the user; the rest ends the command with plain words, the
affected input named, every remedy given, and no implementation term (
doc/planning/cli-git-resident-service.md, “Failures the client can meet”). The texts live here as constants so a test can check each against a list of terms that must never reach a user, and so the text a client prints cannot drift from the text the tests read. - service_
native - The commits real Git builds for the service: an editor, a signature, and what the service does not embed.
What:
The commits real Git builds for the commit service: an editor, a signature, and every command line the service does
not build itself.
Why:
Git owns the editor, the signer and the message semantics of whatever the service cannot embed, so this module points
real
git commitat a thin scratch repository (itsHEADand branch at the base, the policy-settled private index, the real configuration included, every hook off) whose new objects go straight into the real object store. Nothing is locked and nothing is registered: the scratch repository sits inside the commit’s own scratch directory, is removed with it, and the commit it makes is only an object until the service moves the branch to it by compare-and-swap (doc/planning/cli-git-resident-service.md, “Commits Git builds”). - service_
protocol - Messages between the wrapper and the resident commit service.
What:
The messages between the wrapper and the resident commit service, one JSON object per line.
Why:
A client sends a prepared change with a request identity and waits; the service answers with a landing, a queue
position, a request to prepare again, or a failure from a closed list. The line form needs no framing library and
every field is checked on read, so a damaged or hostile line is refused rather than guessed
(
doc/planning/cli-git-resident-service.md, “Protocol and request identity”). - service_
provision - The wrapper’s own provisioning of the commit service’s socket activation.
What:
The wrapper makes socket activation for the commit service exist the first time a commit needs it: it writes the two
unit files into the user’s runtime unit directory, asks the user manager (through the session bus) to reload and to
start the socket, and returns. The manager, not the wrapper, starts the service when the first connection arrives.
Why:
There is no install step to remember (
doc/planning/cli-git-resident-service.md, “Provisioning”): like Bazel’s client, the wrapper sets up what it needs and tells nobody. The units live under$XDG_RUNTIME_DIR/systemd/user, which disappears at logout, so nothing is left behind in the home directory and the next first use provisions again. Every step is idempotent (a file is written, by temporary file and rename, only when its content differs), so several commits racing to provision agree without any lock. Units this wrapper’s earlier releases wrote into~/.config/systemd/useroutrank the runtime ones in the unit search path, so they are recognized by their content and removed; a unit of the same name that something else wrote is never touched, and the commit stops with a message naming the file. - service_
review - The service’s handler for a review work item, bounded by a call deadline and a total budget.
What:
The service’s handler for a
reviewwork item: read the landed commit, ask the decision gateway, and append the entry to the worktree’s shadow log, all inside a fixed time budget. Why: This replaces the repository’s post-commit hook that ranmise run shadow-review(doc/runbook/standards-review-gate.md). The review shares the service’s work thread with pushes and upkeep, so every step is bounded: the gateway call by a deadline, the Git reads and the write by the budget around the whole run. A review that fails leaves its item infailedas a plain record and the log shows the gateway’s failure as an entry; nothing is ever left claimed. - service_
route - Which way a commit goes: through the service, set up first, or the earlier path.
What:
Which way a commit goes: through the resident service (already listening, or set up first by the wrapper), or the
way it went before the service existed.
Why:
There is no switch and no install state for the person to manage (
doc/planning/cli-git-resident-service.md, “Provisioning”). The route follows the machine: a socket that is there means a service to use; a user session bus and a service program next to the wrapper mean the wrapper can set the service up itself; anything else keeps the earlier path until that path is retired, after which it is a plain failure. The decision reads the file system only and starts no process. - service_
server - The commit service’s loop.
What:
The resident service’s loop: accept connections, queue requests in arrival order, land what is queued as a batch,
answer each client, and exit when told to retire.
Why:
One writer lands every commit, so there is no lock to take, hold or wait for: order is the order of the queue, the
cost of the one durable write is shared by the batch, and a client that waits blocks on its socket instead of
polling (
doc/planning/cli-git-resident-service.md). The service never starts itself: the operating system hands it the listening socket (socket activation), or a development run binds one. - service_
work - The service’s consumer of after-commit push and upkeep work.
What:
The service’s consumer of after-commit work: pending
pushandmaintenancework items of one repository are claimed, coalesced, run, settled, and a push that failed leaves a plain notice for the person’s next commit. Why: A commit must not wait for the network or for upkeep, so the wrapper leaves a record and the resident service does the work on its own thread (doc/planning/cli-git-resident-service.md, “After-commit work”). The service is the only pusher of the repositories it serves, so no lock is needed: several pending pushes of one branch become one push, and a service that died mid-push is noticed by the claim’s lease, never by a process id. Other kinds of work (the standards review) are never claimed here. - shadow_
config - The shadow repository
configfile and the copied repository format. What: The shadow repository’sconfigfile, written by cli-git itself, and the repository format facts it copies from the real common config. Why: Git does not applycore.repositoryformatversionandextensions.*from an included file, so they are copied explicitly. The shadow names no hooks directory (traditional hooks are unsupported, and every shadow command passes its owncore.hooksPath);core.worktree,gc.autoandmaintenance.autofollow the include, so the real config cannot redirect the worktree or start maintenance in the shadow;hook.<event>.enabled = falsefor every hook event follows it too, because the wrapper supports no external hook (src/shadow-repository/shadow-config.ts). - shadow_
create - Creation of the shadow repository of one transaction.
What:
Creates the shadow repository of one published transaction:
an empty bare repository,
its config and alternates,
the linked shared entries,
the conclusion state,
and the ref snapshot.
Why:
The shadow is a separate repository rather than a registered worktree,
so
git worktree listnever shows it and preparation creates no ref in the real repository. Its object store receives the objects nativegit commitwrites, and its alternates name the real object directory, so a realgit prunecannot delete a pending commit (src/shadow-repository/shadow-repository.ts). - shadow_
git - Git commands run against a transaction’s shadow repository.
What:
Git commands run against a transaction’s shadow repository,
and its location and
removal.
Why:
The shadow lives at
<git-common-dir>/cli-git/shadow/<transaction-id>, derived from the transaction ID so recovery finds it beforeprepared.jsonexists. cli-git’s own shadow commands remove every variable that would redirect Git elsewhere and point hooks at an absent directory, so no hook observes private shadow writes (src/shadow-repository/shadow-refs.ts,runShadowGit). - shadow_
links - The shadow repository links to the real shared entries.
What:
Links every non-private entry of the real common Git directory into a shadow repository,
and copies the per-worktree entries (
config.worktree, a linked worktree’s sparse checkout). Why: Shared entries such ashooks,info,rr-cache,lfsandmodulesmust be the real ones, so hooks and native Git see them; transaction-private entries (refs, index, objects, conclusion state, lock files) stay the shadow’s own (src/shadow-repository/shadow-links.ts). On Windows the incumbent uses junctions and copies; this port uses directory symbolic links and copies, because the standard library has no junction API. - shadow_
refs - The shadow ref store: snapshot, private target, symbolic refs and
HEAD. What: The shadow ref store: a snapshot of every real ref, the private target ref, symbolic refs, and the privateHEAD. Why: Hooks resolve tags, other branches and remote-tracking refs as they stood at invocation. With the files backend the snapshot is onepacked-refsfile and the private target is a loose ref, which takes precedence over a packed entry; with reftable the snapshot is oneupdate-ref --stdintransaction (src/shadow-repository/shadow-refs.ts). - shadow_
skeleton - The entries of an empty bare repository, created in-process instead of by
git init. What: The files and directories of an empty bare repository on the files ref backend, created in-process. Why: The shadow repository used to be made bygit init --bare --template=, a Git start whose whole effect is these few entries;create_shadow_repositoryoverwrites theconfigit writes and every later shadow step sets theHEADit writes, so nothing else of Git’s work is used. Creating them here removes one process start from every commit (init; theconfigread that follows it is the wrapper’s own and stays). A torn creation leaves a directory that is not a repository; recovery removes the shadow tree as a whole (binary_recovery_tearing_shadow_tests.rs), exactly as it does for a torngit init. - signal_
relay - Catching SIGINT, SIGTERM, SIGHUP and SIGQUIT while a Git child runs, and relaying only process-sent signals.
What:
Catching
SIGINT,SIGTERM,SIGHUPandSIGQUITwhile the wrapper waits for a Git child, relaying a signal to that child only when a process sent it, and reporting how the child ended.SignalGuardowns the handlers for its lifetime and restores the previous dispositions on drop. Why: A wrapper that dies on a signal leaves its commit transaction for recovery and Git running; one that catches the signal can end Git, release its locks and remove the transaction first. A signal from the terminal already reached Git as part of the foreground process group, so relaying it again would deliver it twice. - stacked_
merge_ push - The push of a merge that integrated a lost race, planned and run inside the running wrapper.
What:
The push of a merge that integrated a lost race, planned and run inside the running wrapper.
Why:
The merge push must pass every gate a typed
git pushpasses, so the policies read the integrated result. An earlier version started the wrapper’s own executable with a marker variable to get those gates; the wrapper never starts itself, so the same planning (plan_invocation) now runs in this process, with the marker set only in the environment handed to the planner, never in the process environment, and the planned action is run here. The marker stops that planned push from stacking again. - standards_
review_ append - The standards-review shadow run: appending an entry to the shared log without a lock, once per commit, with repair.
What:
How the service adds a review entry to the shadow log of a worktree that people and agents are editing at the same
time: append only, never create, never rewrite, once per commit, with a bounded repair of entries another writer
overwrote.
Why:
doc/audit/standards-review-gate-shadow.mdis a tracked file that sessions edit while the service appends to it, and no lock may be taken. OneO_APPENDwrite of the whole entry cannot clobber anyone’s content and cannot interleave with another append. What an append cannot prevent is another writer that read the file before it and writes the whole file back after it, which loses the entry. Each entry is therefore also kept as a plain file in the Git directory, and every delivery checks the latest few entries are still in the log and appends the missing ones again. - standards_
review_ client - The standards-review shadow run: the in-process HTTPS call to the decisions gateway, with a total deadline.
What:
The one HTTPS call of the standards-review shadow run, made in-process with a total deadline.
Why:
The root
mise.tomltaskshadow-reviewcallsfetchwith a 30 s signal. This port calls the same endpoint from the wrapper itself, so nonodeormiseis started. The deadline covers connecting, TLS, sending and reading the body, so the call can never hold a caller longer than the deadline. The trust roots are a parameter: production uses the roots embedded in the binary, a test passes the fixture authority of its local TLS server. - standards_
review_ entry - The standards-review shadow run: log entry rendering and appending.
What:
The log entries of the standards-review shadow run, rendered as the root
mise.tomltaskshadow-reviewwrites them, and their append to the log file. Why:doc/audit/standards-review-gate-shadow.mdalready holds that task’s entries, and readers and scorers parse them, so the port writes the same lines. The script appends line by line; this port appends each entry in one write, so entries of two concurrent runs cannot interleave. - standards_
review_ input - The standards-review shadow run: which commits get an entry, and the capped model input.
What:
The pure part of the standards-review shadow run: which landed commits get an entry, and the capped model input
built from the commit’s patch and the excerpts of its changed code files.
Why:
This is the root
mise.tomltaskshadow-reviewafter its Git reads: the no-entry, no-code-files and review cases, the 2000-line patch cap, the eight-file and 4000-line excerpt caps, and the 24000-unit input cap. Taking the patch and the file contents as plain values lets the caps be tested without Git, a network or a clock. - standards_
review_ patch - The standards-review shadow run: a file’s changed old-side ranges read from the patch.
What:
Reading one file’s changed new-side line spans out of a
git show --patchtext, so the review needs nogit diff -U0start per excerpted file. Why: The script runsgit diff -U0 <sha>^ <sha> -- <name>for each of up to eight files. A hunk of the three-line-context patch is a list of change runs, and each run is one zero-context hunk, so the same spans follow from the patch the review already holds. The spans are on the new side because the excerpt is cut from the new-side file. - standards_
review_ request - The standards-review shadow run: the Decisions API request body and the answer reading.
What:
The Decisions API request body the standards-review shadow run sends, and the reading of the gateway’s answer into
the verdict and the
Findings keptline. Why: The rootmise.tomltaskshadow-reviewbuilds this body withJSON.stringifyand reads the answer withJSON.parse; the body must keep its key order and escaping so the same input sends the same bytes, and the answer text must print numbers and failures the way the log’s readers already know. - standards_
review_ run - The standards-review shadow run, end to end, and its bounded runner. Nothing calls it yet.
What:
The standards-review shadow run, end to end and in-process: classify the landed commit’s names, read the commit,
build the capped input, call the gateway, and append the one log entry. Also a bounded runner that gives a caller a
hard time limit over all of it.
Why:
This replaces the root
mise.tomltaskshadow-review(its Luna route; theSHADOW_REVIEW_ALLOW=1route startspiand is not ported). The review must never delay or fail a landing, so nothing here panics or returns a failure the landing would act on, andrun_withinabandons the work when the time is up. Nothing calls this yet. - standards_
review_ source - The standards-review shadow run: the two real-Git reads (patch and file texts) of a landed commit.
What:
The Git reads of the standards-review shadow run, in as few real-Git starts as the data allows: one
git showfor the patch and onegit cat-file --batchfor the new-side text of the excerpted code files. Why: The rootmise.tomltaskshadow-reviewstarts Git twenty-odd times per landing (rev-parse,log,show,diff-tree, thenshowanddiff -U0for each of up to eight files). The landing already knows the commit name, its message and the changed names, so the subject and the names need no start, the zero-context ranges come out of the patch, and the file texts come in one batch. A commit with no code files costs no start at all. - standards_
review_ text - The standards-review shadow run: JavaScript text behaviors the log depends on (UTF-16 lengths, number text).
What:
The few text behaviors of the standards-review log that JavaScript defines and Rust does not share:
UTF-16 lengths and prefixes,
Numberto text,toFixed(1)of a duration, and Git’s%ssubject. Why: The rootmise.tomltaskshadow-reviewmeasures and cuts text in UTF-16 code units and prints numbers the way ECMAScript does, and its log is compared entry by entry with this port’s, so the same text must give the same counts and the same bytes. - start_
retry - Starting a child again when the operating system refuses it for lack of processes, within a bounded wait.
What:
Starting a child process again when the operating system refuses it for lack of processes.
Why:
A host that has run out of processes (a process limit, a cgroup limit) refuses a start with
EAGAIN, which Rust reports asErrorKind::WouldBlock. A refused start started nothing, so starting again is always safe, and such a shortage usually ends within moments. Without this the first refusal ends a commit that the next attempt would have finished. The wait is bounded, so the wrapper still never stalls: a shortage that outlasts the budget fails with the same error as before plus how often the start was refused. - systemd_
dbus - A minimal D-Bus client for the user session bus, for the provisioning’s two calls.
What:
A minimal D-Bus client for the user session bus: authenticate as the current user, say
Hello, and call methods oforg.freedesktop.systemd1.Managerwith string arguments, each bounded by one deadline for the whole exchange. Why: The wrapper provisions socket activation itself, and may start no program to do it (systemctlwould be one). The supported surface of the user manager is its D-Bus API (org.freedesktop.systemd1(5)), reached through the session bus; the manager’s own private socket is documented as private to systemd. OnlyReloadandStartUnitare needed, both with at most two string arguments, so the writer and the reader below cover exactly the types those calls and the bus’s replies use (s,o,g,u) and stop at anything else. The reader is total over arbitrary bytes (a fuzz target holds it to that). The comparison with a D-Bus crate is indoc/planning/cli-git-resident-service.md, “Provisioning”. - traditional_
hook_ warning - The non-blocking warning for a configured
core.hooksPath, decided from the configuration snapshot alone. What: The non-blocking warning a wrapped commit prints when the configuration setscore.hooksPath. Why: Traditional hooks (executable scripts in.git/hooksor in acore.hooksPathdirectory) are unsupported, and their behavior with the wrapper is undefined. The commit path may not look for them: a directory scan would cost file system calls on every commit. It already holds one free signal, the configuration snapshot it read for other purposes, so a configuredcore.hooksPathis the only trigger, decided from memory alone. Finding the scripts themselves is the job ofgit cli-git doctor. The line is aconfiguration-warningevent, which never blocks a command. - transaction_
gate - Startup recovery as a step of a wrapped or direct command.
What:
Startup recovery as a step of a wrapped or direct command:
recover,
then report how the
command may go on.
Why:
Every command that reads the repository location recovers dead transactions first.
A
guarded command beside a live transaction is still refused until index writers coordinate
with live transactions;
a read-only command goes on.
A legacy directory or a recovery
failure stops the command with exit 2,
as the incumbent’s
content-unavailableengine failure does. - transaction_
git - Running real Git for transaction,
recovery and landing work.
What:
Running real Git for transaction,
recovery and landing work:
captured output,
an
optional private index and object store,
extra or removed variables,
and bytes on
standard input.
Why:
Every transaction step asks Git a question or writes an object;
one runner keeps every
such start an argument array (never a shell string) carrying the wrapper’s child
environment overlay (lock PID injection),
as the incumbent’s
runTransactionGitdoes. - transaction_
journal - The schema-version-2 journal records of one commit transaction and their filenames.
What:
The schema-version-2 journal records of one commit transaction and their filenames;
encoding and parsing live in
transaction_journal_encode.rsandtransaction_journal_parse.rs. Why: State files are created exclusively and never rewritten, so a crash leaves the newest complete state readable; recovery in either wrapper reads the records the other wrote (src/policy-engine/commit-transaction-journal-states.tsandcommit-transaction-journal-parse.ts). Every missing or mistyped field fails closed with the record and field named. - transaction_
journal_ encode - The exact encoding of every journal record.
What:
The exact encoding of every schema-version-2 journal record:
compact JSON in the
incumbent’s field order,
one record per line.
Why:
writeJournalRecordwritesJSON.stringify(record)and a newline; the native wrapper writes the same bytes for the same values so either wrapper can recover the other’s transactions. - transaction_
journal_ parse - Strict parsers of every journal record.
What:
Strict parsers of every schema-version-2 journal record.
Why:
Recovery reads records either wrapper wrote;
every missing or mistyped field fails
closed with the record and field named,
exactly as the incumbent’s
commit-transaction-journal-parse.tsandcommit-transaction-journal-reader.tsdo. - transaction_
lease - The preparation and landing leases nested wrapper invocations inherit.
What:
The landing lease:
a capability a running wrapper passes to the
Git it starts,
so a nested wrapper invocation from
rebase --execproceeds under its ancestor instead of waiting for it. Why: Without a lease a nested invocation would wait for the landing lock its own ancestor holds, forever. Both wrappers format and validate the same lease (src/index-lock/landing-lease.ts). The wrapper runs no external hook, so it passes no preparation lease any more. - transaction_
owner - The owner record of one commit-transaction directory and its liveness rule.
What:
The owner record of one commit-transaction directory (
owner.json, schema version 2) and the liveness rule that decides whether recovery may touch the transaction. Why: The owner record is written before the directory is published, so recovery can skip a live owner’s transaction at every phase. Both wrappers write and judge the same record (src/policy-engine/commit-transaction-owner.ts). - transaction_
phase - Test-only transaction phase markers, compiled into the release executable and armed only by an environment variable.
What:
Test-only transaction phase markers for crash and interleaving suites.
An explicit environment variable arms one marker:
CLI_GIT_TEST_ONLY_PHASE_SIGNAL=<phase>:killends the process withSIGKILLat the phase,<phase>:kill:<directory>writes<directory>/<phase>.reachedfirst, and<phase>:pause:<directory>writes that marker and waits until<directory>/<phase>.releaseexists. A repeatable phase numbers its files by occurrence:<phase>-<n>.reached. Why: Crash suites need a deterministic point to kill the wrapper. The marker is compiled into the release executable, but only the explicit variable arms it, and a malformed value fails the invocation at every phase it reaches instead of being ignored (src/policy-engine/commit-transaction-test-phase.ts). - transaction_
registry - The per-worktree registry of commit-transaction directories.
What:
The per-worktree registry of commit-transaction directories,
<git-dir>/cli-git-transactions/: entry names, publication by rename, removal by rename, and the strict listing recovery reads. Why: Each transaction owns<registry>/<uuid>/, published only onceowner.jsonis complete, so every published directory names its owner; removal renames it to a retired name first, so a reader never sees a published directory lose its owner. Both wrappers create and read the same registry (src/policy-engine/commit-transaction-registry.ts). - unported
- The work of the installed cli-git that is not ported, and the refusal notice. What: The work of the installed cli-git that this executable does not do yet, as one typed list, and the notice printed when a command needs any of it. Why: A command that needs unported work must stop with exit status 2 and say what is missing. Forwarding it instead would run the command without the protection the installed wrapper gives it, and nobody would see that it happened.
- wait_
progress - The shared clock of every wait notice: the grace, the repeat interval and a queued commit’s place in line.
What:
The shared clock of every wait notice:
when the first line of a wait is due,
how often it repeats,
and the place in line a queued commit holds.
Why:
A commit that queues behind other commits, a lock, a reservation or a child process must say so,
and say it again at a steady interval,
or the caller cannot tell a queued commit from a hung one (issue 1811).
Each wait loop feeds its elapsed time to a
Tickerand prints a line when the ticker says one is due, so the decision is a function of elapsed time alone and a test can drive it with chosen durations. The interval comes fromCLI_GIT_WAIT_NOTICE_SECONDSat the production entry of each wait. - work_
item - The durable work-item record for asynchronous work (review, push, maintenance) left for a resident consumer.
What:
The durable work-item record: a small file in the Git directory that says “this landed object needs this
asynchronous work”, written by the wrapper and drained by a separate resident consumer.
Why:
A wrapper process exits when its command ends, and it may start no program, so work that must not delay a landing
(the standards review, later auto-push and maintenance) is left as a record instead. The record is generic: a kind,
the landed object and an id derived from both, so a retry cannot duplicate it. The design, its failure cases and
the consumer contract are in
doc/planning/cli-git-standards-review-port.md. Nothing calls this yet. - worktree_
completion - Bringing added paths’ worktree copies to their landed bytes.
What:
Bringing each added path’s worktree copy to the landed bytes after a commit landed,
with
descriptor-bound checks that never overwrite a concurrent edit.
Why:
An added path’s committed bytes differ from its worktree copy (a policy corrected it);
the
copy is replaced only while it still holds the original bytes,
through a same-directory
temporary file.
A copy holding neither is kept and reported,
since the commit already
landed (
src/policy-engine/commit-transaction-added-paths.ts,-worktree-check.ts,-worktree-replace.ts). - worktree_
copy - The linked-worktree ignored-state copy around one forwarded Git invocation: the integrator’s entry point. What: The linked-worktree ignored-state copy around one forwarded Git invocation: the public entry point the integrator calls, the settlement and summary it writes, and the exit status it returns. Why: A worktree that Git creates (or a bare repository’s) needs its ignored files from the source worktree, copied under one settlement lock per repository and recovered first when an earlier copy was interrupted.
- worktree_
copy_ compare - Exact comparison of manifest entries against the filesystem, and the source stability check after staging. What: Exact comparison of one manifest entry against the filesystem: its kind, its retained mode, its symbolic-link target text or its regular-file bytes. Also the final check that the source still matches the stage after staging. Why: A destination must receive exactly what was staged from the source, and a source that changed during the transfer must block the destination.
- worktree_
copy_ error - The one failure type of the linked-worktree ignored-state copy and its diagnostics. What: The one failure type of the linked-worktree ignored-state copy, with the complete user-facing diagnostic in its message. Why: Every copy step fails with a message that names the path it could not handle and, when there is one, the operating system’s cause. The incumbent renders the cause after the message with a fixed separator, so both wrappers print the same words.
- worktree_
copy_ forward - Which forwarded invocations the copy applies to, and the child run that carries its settlement lease. What: Running a forwarded Git command through the linked-worktree ignored-state copy: which invocations the copy applies to, and the child-process run that carries the settlement lease. Why: A command that creates or moves worktrees must stay under the settlement lock while real Git runs and the ignored state is copied, and a command run from a linked worktree must first recover an interrupted copy; replacing the process with Git would drop both duties, so these invocations run Git as a child. The main checkout is a source only for the configured rules file, so it takes this path only when the command creates a worktree and the configuration names that file.
- worktree_
copy_ install - Installation of a validated stage into a new worktree, with preflight, batches and rollback on failure. What: Installation of one validated stage into a newly registered worktree: preflight of every existing destination entry, batched creation with its durable records, mode application and rollback of this installation’s own paths when it fails. Why: A destination that already holds an identical entry is accepted, and a differing entry is never overwritten, so the copy cannot destroy user state. Recording each batch before and after it runs bounds the work an interrupted run leaves unrecorded.
- worktree_
copy_ install_ entry - The exclusive creation of one destination entry from the stage, and the identity captured after it. What: The exclusive creation of one destination entry from the private stage, the identity captured right after it exists, and the creation of the unselected parent directories an entry needs. Why: A regular file is installed as a hard link to its staged copy, so it appears with its final bytes and mode in one step; the identity captured after creation is the only proof later rollback accepts.
- worktree_
copy_ install_ log - The append-only install log of one stage: per-batch intents and creations, with resumption. What: The append-only install log inside one transaction’s private stage: one synced JSON line per batch of intents and one per batch of proven creations. Why: The journal header changes only with the phase, so the per-batch records live here; bytes written then grow with the entry count instead of with the square of it.
- worktree_
copy_ journal - The durable journal of one copy: exact encoding, strict validation, private storage and removal.
What:
The durable journal of one destination’s linked-worktree copy:
its exact JSON encoding, strict validation of every field recovery will act on,
private storage under
<common>/cli-git-worktree-copy/v1, and removal of a finished transaction’s stage and journal. Why: A crash can happen after any step, so the journal must say which phase the copy reached and which paths it may have touched, and recovery must never act on a journal it cannot prove is its own. - worktree_
copy_ lock - The settlement lock that serializes copy installation and recovery, its owner record, and inherited leases. What: The settlement lock that serializes worktree-copy installation and recovery per common Git directory, its owner record, liveness-based retirement of dead owners, and the inherited-lease check nested Git runs use. Why: Two copies into one repository must not interleave, but a commit in a linked worktree must never wait for them; only an owner proven alive may be waited for, and only an owner proven dead may be replaced.
- worktree_
copy_ manifest - The deterministic parent-first manifest of a copy, and the mode application to a staged copy. What: The deterministic, parent-first list of entries a copy transfers, read without following symbolic links, and the application of the retained permission bits to a staged copy. Why: The stage and the source are compared entry by entry, so both sides must enumerate in the same order and classify the same way. Sockets, FIFOs and device nodes stop the walk before any of them is opened.
- worktree_
copy_ recovery - Recovery of interrupted copies: registration checks, discards with notices, resumption, and the pending check. What: Recovery of interrupted linked-worktree copies: every pending journal is resumed, completed, or discarded with a notice; malformed state fails closed and stays; the pending check that forwarded commands run first. Why: A copy interrupted by a crash must not leave a worktree half-filled forever, but recovery may only act on journals whose destination is still a registration of this repository and whose stage is still private.
- worktree_
copy_ registry - Which linked worktrees a worktree-creating Git command registered, before and after it ran.
What:
The linked worktrees a worktree-creating Git command registered:
the administrative identities before real Git ran,
the identities after it returned,
and the canonical root of each new one.
Why:
A worktree that Git registered before failing still receives the ignored files,
so the comparison runs after Git returns whatever its status was.
The main worktree is observed only when the caller names repository paths to copy from it
(
observe_copy_source), so its other invocations cost no filesystem reads. - worktree_
copy_ rollback - Removal of the paths a failed installation created, only where their identity and content still prove them cli-git’s own. What: Removal of the paths one failed installation created, child first, and only where the filesystem still proves they are cli-git’s own. Why: A failed installation must not leave a partial worktree, but it also must never delete a path the user or another process put there after cli-git created it.
- worktree_
copy_ selection - The ignored source paths a linked-worktree copy selects, and the path validation every selection passes. What: Which source paths the linked-worktree copy selects: the ignored roots Git reports under the standard exclusion stack, the ignored special entries (sockets, FIFOs, devices) Git’s listing omits, and the validation every repository path passes before it touches the filesystem. Why: Git decides what is ignored; the copy only adds the entries Git’s index-oriented listing cannot report, and it never trusts a path that could leave the worktree.
- worktree_
copy_ stage - The private, validated stage of one source’s ignored state beside a destination worktree.
What:
The private, validated copy of one source’s ignored state,
staged in a mode-
0700directory beside a destination worktree. Why: The stage sits on the destination’s filesystem, so a file clone stays local and the install is a rename or a link. The source is copied entry by entry and compared again at the end, so a source that changed during the copy never reaches the destination. - worktree_
copy_ stage_ claim - The durable claim on a stage container’s name, and the removal of stages an interrupted copy left behind. What: The durable claim on a stage container’s name, written before the container exists, and the cleanup of containers whose copy was interrupted before its journal was written. Why: Staging a worktree’s ignored state takes minutes and gigabytes, and the journal is written only after it. A process killed in between (a signal, the out-of-memory killer, a power loss) leaves a stage nothing names. The claim names it first, so the next recovery pass, which runs only when no live copy holds the settlement lock, removes it with a notice instead of leaving it to fill the disk.
- worktree_
copy_ transaction - One journaled installation to an end, resumption from the log, and synchronization of a created worktree. What: One destination’s journaled installation to an end, and the synchronization of every created worktree. The journal records the phase, the install log records each batch’s claims and creations, and a finished or failed installation always removes its journal and stage. Why: A failed installation must not stay pending, because recovery would replay the same failure before every later command; an interrupted one must resume from exactly what it recorded.
- worktree_
identity - Repository and worktree identity as reported by real Git.
What:
Ask real Git which repository and worktree an invocation selects.
Why:
Configuration lives at the worktree top level,
and worktree policies need to
know main versus linked.
Git applies
-C,--git-dir,--work-treeand its environment itself, so the wrapper never re-implements that selection. - wrapped_
command - The lifecycle of one wrapped Git command, from refusals to forwarding. What: Decide what one wrapped Git command does once real Git is known: forward it, possibly rewritten, or stop with policy events or a refusal. Why: This is the whole lifecycle of a forwarded command in one place and in the installed wrapper’s order: unported work is refused, configuration is loaded only for commands that need it, the policy pass runs, and a real push is stopped until its own lifecycle exists. A read-only command never reads configuration and asks Git at most where it runs.
- wrapper_
controls - Wrapper-only control spellings, their meanings, and removal before the subcommand. What: The wrapper-only control tokens, what each one means, and their removal from the arguments that come before the Git subcommand. Why: Git refuses options it does not know, so every control must be gone before Git runs, and every policy decision must read the arguments without them. A control is recognized only where Git itself would look for an option, and it is removed by its position, never by its spelling, so an equal-looking value or path is kept.
- wrapper_
invocation - Removal of every wrapper control from one invocation, by position. What: Remove every wrapper control from one invocation, before and after the subcommand, and say how the command’s own arguments were read. Why: Detecting a control and removing it must be one act. This module is the only place that removes wrapper tokens: positions come from Git’s own option table of the command where one is ported, and are deleted by position, so a control never reaches Git and a message, value or path that spells a control is never removed.