Skip to main content

Crate git_policy_cli

Crate git_policy_cli 

Source
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 types git --version should 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.md rule APG), and the incumbent’s src/auto-push.ts decides the push argv from the upstream state, prints only GitHub remote: 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-pushed record 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 main can 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 push of 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-tree and git commit-tree read 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-pushed record: validation, tolerant reads and atomic publication. What: The per-branch last-pushed record 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-pushed record 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’s src/auto-push-single-flight.ts decides 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 one git 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 push with a deadline, its low-speed abort and the lock wait limit derived from it. What: Run the auto-push’s real git push with 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 text Git attribute unset, read from the state the candidates were listed from. What: Which candidate paths have their text Git attribute unset (-text, which the binary macro includes), resolved by git check-attr against the same state the candidates were listed from. Why: A path whose text attribute 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 .gitattributes staged 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 --batch reply. What: Read one git cat-file --batch reply 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 add would stage, and the worktree files a direct command selects. What: The two candidate sources that need a private index: what git add would stage, and the worktree files a direct git cli-git check or fix selects. Why: Both are answered before Git touches the real index. git add is replayed on a private copy of the index, and the candidates are the entries the replay changed, as the installed wrapper’s add-policy-facts.ts and add-staged-delta.ts define them. A direct command stages its scope with git add --all on a private copy, as direct-check-facts.ts does, 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 add would 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’s index-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 --batch process 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-index and git 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 --stage records and the delta between two index states. What: Parse the NUL-delimited stage records of git ls-files --stage -z, find the paths one staging operation changed, and turn them into candidate records. Why: git add is 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, invalidate retires 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 with HEAD: it reads each workspace manifest’s HEAD bytes, and a direct fix may change a tracked file it did not select only when HEAD holds 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 symbolic HEAD target, the conclusion kind, the ref storage backend, and the preparation base. Why: Every later read of HEAD inside a transaction uses the recorded base or the landed commit, never live HEAD, 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, captureInvocationLayout and captureInvocationBase).
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, and captured.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-parse held a commit for as long as a stalled pack-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 --refresh and ls-files --modified took 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 own git commit with 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 --batch and 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 add and its bulk-staging facts. What: The complete git add option 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 -A inside 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 checkout and git switch. What: Branch-creation facts of git branch, git checkout and git switch regions, 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 of git branch in Git 2.56.0. Why: Whether git 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 follow options[] in cmd_branch (builtin/branch.c:992-1050) in source order.
command_checkout_table
The complete Git 2.56.0 option tables of git checkout and git switch. What: The complete option tables of git checkout and git switch in 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 clean and its deletion facts. What: The complete git clean option 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-run wins, in clusters (-ndX) and abbreviations (--dry).
command_commit
Facts about the arguments after git commit. What: Facts about the arguments after git 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-all reports all off, exactly as git commit would act.
command_commit_table
The complete Git 2.56.0 option table of git commit. What: The complete option table of git commit in Git 2.56.0. Why: Declaring every option Git accepts removes the guess about whether an undeclared option consumes the next token. Rows follow builtin_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 config region. What: The form and file scope of a git config region, read as Git 2.56.0 reads it. Why: The require-root policy exempts git config when it works on the per-user or system file, or lists. Whether --global is that option or a value being stored depends on the form: git config user.name --global writes the text --global into the repository’s own configuration.
command_config_table
The Git 2.56.0 option groups of git config. What: The option groups of git config in Git 2.56.0, one constant per group. Why: Git builds each git config subcommand’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-tree and the --abort forms of merge, rebase, cherry-pick, revert and am can 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 --force is 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-options tokenization of one command’s argument region. What: Read one Git command’s arguments the way Git 2.56.0 parse-options.c reads 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 push and its facts. What: The complete git push option 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 reset and its mode. What: The complete git reset option table of Git 2.56.0 and the mode read from it. Why: The linked-worktree policy guards resets that rewrite worktree files: --hard, --merge and --keep. The five mode options write one variable, so only the last one counts, in any abbreviation Git accepts.
command_stash
git stash subcommand dispatch and wrapper-flag positions. What: Which git stash subcommand 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 stash subcommand. What: The option tables of every git stash subcommand in Git 2.56.0. Why: git stash dispatches 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 complete git status option table of Git 2.56.0, the machine-readable fact read from it, and the reading of advice.statusHints set 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 worktree invocation creates or moves a worktree. What: The one fact the wrapper needs about git worktree: whether the invocation creates or moves a worktree. Why: Only git worktree add and git worktree move are 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, -a removal and private arguments. What: Argument transforms of native preparation: the normalised commit argument region, the split of global options from the commit region, the removal of -a/--all, and the arguments of the real git commit that run against the complete private index. Why: Native git commit must 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 of lastIndexOf, so a -m value equal to a pathspec is removed from the same position.
commit_command
The git commit command: the transaction, the settled events, the post-commit gate and the backup. What: The git commit command 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.ts runs these steps in this order: the tree is written from the stable private index, the dispatcher shim is shared by preparation and the post-landing post-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.reserveAfterLostRaces lost 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 refusing git commit until 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 completeNoChangeTransaction lands 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 commit in the shadow, the prepared commit read back and prepared.json. What: Native git commit in the shadow repository against the private commit index, the prepared commit read back from the shadow, and prepared.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 HEAD at the replay parent, the policies converged again over that index, and pre-commit re-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, so git add applies the ignore rules: a directory pathspec silently matches nothing, and a file pathspec fails with a hint to force-add that commit does not accept. Carrying the real index’s entry first makes the path tracked in the private index, and git add then 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 --cached for 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 private git add fails 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 commit removes MERGE_HEAD and 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 commit in 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.rs holds the landing side). Pseudorefs of a reftable repository live in the ref store and are copied with update-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.jsonc content 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 read cli-git.config.jsonc at 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.jsonc text 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 rulesFile option: a repository-relative name that stays inside the repository. What: The rulesFile option of security/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.rs reads 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, then FORBIDDEN_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.jsonc produces, with defaults. Why: The policy engine and transactions read plain typed values, never JSON.
config_snapshot
One git config --list read, 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 starting git config for each key. Why: Every git config start 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, then GIT_CONFIG_COUNT, GIT_CONFIG_PARAMETERS and -c values, all in the command scope), so the last entry of a key is the value git config --get prints. 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) is Lookup::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::DefaultHasher is 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 is policy-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 after from, import, import( or require(. 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 HEAD commit’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 with JSON.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 follow JSON.parse: the last duplicate wins. A manifest is decoded as strict UTF-8 after one optional byte-order mark, as the incumbent’s fatal TextDecoder does.
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) and isNonTestSourcePath (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 of mono/dependent-version-bump: when to plan, and the findings a plan becomes. Why: The plan is a pre-commit step. Another forwarded command such as git add could not apply its patches, so it plans only for git commit among forwarded commands, and for git cli-git check and git 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 under packages:, so a line scan reads exactly what it wrote, as the incumbent does (publishable-names.ts:38-81). Lines are trimmed with JavaScript’s String.prototype.trim set of characters.
dependent_version_release
Patch bumps of plain release versions and JSON.stringify quoting. What: Patch-bump a plain major.minor.patch release version, and quote text the way JSON.stringify does. 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 version value 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=true and warnings unless MONOCHROMATIC_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’s direct-fix.ts and direct-fix-convergence.ts decide 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 is policy_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 fix settled on into the worktree, all of them or none, and never touch the index. Why: The installed wrapper’s direct-fix-install.ts decides 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 retired hooks.concurrentCommits key. Why: Traditional hooks (executable scripts in <git-common-dir>/hooks or in a core.hooksPath directory) are unsupported, and their behavior with the wrapper is undefined. The commit path warns when core.hooksPath is 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 shim git lfs writes 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 doctor reports from the configuration: configured hooks and the retired hooks key. What: What git cli-git doctor reports from the configuration: the external hooks Git configuration defines, and the retired hooks.concurrentCommits key. Why: The wrapper runs no external hook, so a configured hook is a finding the author must act on, and a hooks key in cli-git.config.jsonc is 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 0 followed by a NUL byte, so the name depends on the object format alone and never on the repository’s contents. Spawning git hash-object -t tree --stdin for a constant cost a process on every commit.
entry
The per-invocation decision the thin executable performs. What: Decide what one git invocation 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.rs deletes 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 pathBytes when 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 keeps path as text with replacement characters, so every existing reader works unchanged, and adds an optional pathBytes field, 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 of doc/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>.event with hook.<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 in cli-git.config.jsonc. A configured hook that silently never ran would look like a broken check, so a commit prints one configuration-warning event naming the keys and the events, decided from the snapshot the commit already holds (no process start), and git cli-git doctor lists the same hooks.
filesystem_identity
The filesystem identity recorded with every real index.lock a transaction creates. What: The filesystem identity recorded with every real index.lock a transaction creates, spelled exactly as the incumbent’s @monochromatic-dev/fs-id-core spells it. Why: Recovery removes a real index.lock only when its device, inode and filesystem identity all match the journal; the incumbent compares the recorded fsId with 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_fsid of a path asked of the kernel in-process instead of by starting stat. What: The f_fsid of the filesystem holding a path, asked of the kernel in-process and spelled exactly as GNU stat --file-system --format=%i spells it. Why: A filesystem without a UUID (overlayfs, tmpfs) is identified by this text, and the identity of a real index.lock is resolved again when the lock is released. Starting stat for 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 plain git add stuck on it; the same call in-process needs no process, and also saves the stat starts 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 starting findmnt. Why: findmnt --target <path> --output=UUID --noheadings is 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 spawns findmnt as before. doc/planning/cli-git-spawn-and-sync-reduction.md (section “Filesystem identity without findmnt”) 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> or alias.<name>.command, splits it with split_cmdline quoting, 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, so git -c alias.c=commit c is 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_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 version prints, keyed by the identity of the Git executable that printed it. Why: Every commit starts git version to 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 named git: a script, or a shim such as a version manager’s git link 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.hooksPath pointing 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>/hooks or a core.hooksPath directory; git lfs writes its four shims there again whenever it cleans a file) and not a hook defined in the configuration (hook.<name>.event with hook.<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=false for every hook event Git knows, which switches every configured hook off whatever its name. core.fsmonitor is 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 -c or --config-env of 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, so pre-push is 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’s gitformat-index documentation: a 12-byte header, entries (versions 2 to 4), then extensions and a trailing checksum.
index_lock_evidence
Evidence about a foreign index.lock and the verdict it supports. What: Evidence about a foreign index.lock and 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’s core.lockfilePid file 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 native git commit keeps index.lock without a descriptor through its hooks and editor (src/index-lock/index-lock-evidence.ts).
index_lock_holder_parsers
Parsers of lsof -F pcDi records and Restart Manager lines, matched by device and inode. What: Parsers of the holder-scan output that is not /proc: lsof -F pcDi records 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>/fd on Linux, lsof on 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.lock under 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: /proc and Git-free lock reads, real sleeps, operating-system randomness and standard error. Why: WaitEffects is 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, reset except --soft, stash, checkout, switch, merge, rebase, cherry-pick, revert, apply --cached and apply --index, update-index, read-tree, am, pull and sparse-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.lock is 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 example git 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/fix need the same answer: what does the cli-git.config.jsonc of 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’s Date.prototype.toISOString: YYYY-MM-DDTHH:MM:SS.mmmZ. Why: Transaction owner records carry createdAt and capture records invokedAt in 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.md shows 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’s str::trim uses a different whitespace set (it trims U+0085 and keeps U+FEFF).
json_record
Compact JSON records read and written with the incumbent’s JSON semantics. What: Reading and writing the compact JSON records both wrappers share, with the incumbent’s JSON.parse and JSON.stringify semantics. 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.ts runs 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, the index-installed marker and conclusion cleanup. Why: A landing is serial under the landing lock and the real index.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-commit finding for a landing that landed nothing. What: The concurrent-commit finding for a landing that landed nothing because HEAD was 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-parse that 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 .keep file 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 real HEAD reflog.
lexical_path
Lexical path normalization. What: Lexical path normalization, the way Node’s path.resolve does it. Why: git cli-git doctor scans 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 bounded git push of 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 typed git push does. The wrapper is the parent of both children and captures their output, so it owns the explanation: a missing git-lfs is 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 and git-lfs is 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-regexp start 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 no git-lfs process 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 (where git-lfs push words the failure and honours lfs.allowincompletepush), the plan stays as it was and git-lfs scans.
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 from git-lfs. Why: git-lfs names the LFS server in its errors, and an lfs.url written as https://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-lfs decides it. What: Whether a blob is a Git LFS pointer, decided the way git-lfs 3.8.1 decides it when it scans a push. Why: The upload step finds the LFS objects the pushed commits introduce without starting git-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 names git-media, hawser or git-lfs (lfs/pointer.go, matcherRE), lines in the order version, oid, size with a known version, an sha256: object name of 64 lower-case hexadecimal digits and a non-negative decimal size; a pointer of size zero is skipped by prepareUpload (commands/uploader.go). The path of the blob and .gitattributes play no part. A spelling the port is not sure about (extension lines, non-ASCII edges) is a decline, so the caller falls back to git-lfs itself.
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 push starts 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 starts git-lfs only 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, returns None, so the caller asks git-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-push shim git lfs install writes cannot be relied on. Without an upload step a push publishes pointer files whose objects the LFS server never received. The wrapper therefore runs git-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-lfs can 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 by git-lfs push, which the parent starts. When no directory of PATH holds git-lfs, one stat per PATH directory, 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 push runs are tried again (server errors only), after how long, and the notice. What: When a failed git-lfs push is 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, and git-lfs treats 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 runs git-lfs push itself, 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 in lfs_upload_plan. The child sees what the caller gave Git: the global -c options travel as GIT_CONFIG_COUNT pairs, --git-dir, --work-tree and --namespace as their variables, -C as the working directory, and the real Git’s directory leads its PATH so a git it starts is never the wrapper while git-lfs itself is the one the caller’s PATH finds 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 push takes and the refs whose objects it uploads, with --all wherever 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 push takes 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 with git for-each-ref and 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 HEAD names, 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 one config --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-regexp records, and choosing whether git maintenance run --auto runs and whether it detaches. Why: Native git commit runs this step after updating HEAD; a landing replaces that commit with plumbing, so the pack count grows by one per commit unless this step runs. The rules mirror Git’s prepare_auto_maintenance in run-command.c, including JavaScript number semantics for values the incumbent reads with Number(...).
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 with hook.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: one git config read for the maintenance keys (or the commit’s configuration snapshot), then git 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-git decision: help, retired commands, and direct check/fix. What: Decide what one git cli-git ... invocation does. Why: Help, refusals and retired commands need no repository; check and fix validate 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-git argument grammar. What: The grammar of git cli-git <command>, the wrapper’s own management namespace. Why: check and fix run policies over an explicit scope; doctor lists the traditional hooks a repository holds; trust, untrust and status are retired with executable configuration and only explain that.
management_selection
The refusal of a direct command whose --policy selects a policy that is off. What: The --policy selection of a direct git cli-git check or fix: 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 which cli-git.config.jsonc key 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 exit 2.
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 --porcelain output, 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 a To <url> line, then one tab-separated line per ref (<flag>\t<from>:<to>\t<summary>), then Done. With core.abbrev=no the summary of an update holds the full old and new object names (old..new, or old...new when forced); a new ref, a deletion and a refusal hold a bracketed word instead.
manual_push_probe
The updates a manual git push would send, learned from Git’s own dry-run report and confirmed against the remote. What: The authoritative ref updates a git push would 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 --porcelain reports each destination and ref update (manual_push_porcelain), the local objects come from that report or from one rev-parse, and git ls-remote --refs confirms 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/sh hook (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 exclude patterns of markdown/autofix, matched as the linter matches them. What: The exclude option of markdown/autofix: gitignore-syntax patterns, relative to the repository’s top level, naming candidates the policy leaves alone. Why: The installed wrapper filtered candidates with the ignore package 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 the ignore crate’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-lint linked in-process, guarded by a deadline, once per candidate. What: The linter the executable gives markdown/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 entry monochromatic_lint::run_embedded::fix_source is the executable’s --config --stdin --stdin-filename --fix run 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.rs replaces 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/autofix policy hands monochromatic-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 at warn, 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-lint fix 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 --fix run with its one-rule configuration: status 0 with the fixed source and warning findings, because the policy’s configuration gives the rule severity warn; 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 from HEAD at all (measured in doc/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 from git merge-tree --write-tree, the commit from git commit-tree, and the checkout moves with git 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, which git merge itself uses, fed the description line git merge builds for the commit-ish (branch 'x', tag 'x', remote-tracking branch 'x' or commit '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 merge command lines the wrapper completes in isolation, and those it leaves to native Git. What: The shape of a git merge command 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.json but not the name owner.json in 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 outlives FOREIGN_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 own owner.json name 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 .keep files protecting a landing’s migrated pack. What: The .keep files that protect a landing’s migrated pack until the compare-and-swap, named by the message cli-git <transaction-id>. Why: git index-pack --keep=<message> writes the .keep before the pack becomes visible, so no prune or repack drops the objects mid-landing; recovery finds a dead transaction’s .keep by 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-conflict message when it may not. Why: SPEC.md (“Added paths”) admits such a file only when HEAD, 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, assertAddablePath and 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.json is a catalog or workspace reference. What: The optional mono/catalog-specifiers policy: every dependency in a tracked package.json is catalog:, catalog:<name> or exactly workspace:*, and no package.json5, package.yaml or package.yml is tracked. Why: pnpm install accepts 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 the monochromatic-catalog-specifier crate, 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 add has no candidates, so its content policies find nothing. git add and 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-bump check over a lifecycle’s candidate state. What: The mono/dependent-version-bump check: 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, on direct-check and direct-fix; on pre-forward it plans only for a forwarded commit. A real git commit runs through the commit transaction instead of being forwarded, so pre-forward reaches this check only for git 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|fix standard 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-newline policy: 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 whose text Git attribute is unset (-text, which binary includes) 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-strings policy: 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/autofix policy over candidates, behind its linter interface. What: The optional markdown/autofix policy: every Markdown candidate the lifecycle offers is passed through monochromatic-lint --fix with the policy’s rules; a changed source is reported, and corrected on git cli-git fix, and each finding the fixes leave is reported as it is. Why: The installed wrapper’s markdownLintPolicy (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 (.md and .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 check cannot 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.md enters the index. What: The optional mono/forbidden-root-context policy: a CONTEXT.md file 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 --atomic to git push, -o to git commit and -c advice.statusHints=false before git 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 a commit-landed event; the command exits 2. 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’s commit-transaction-conclusion.ts runs 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.ts defines 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 in doc/handover/cli-git-rust-implementation.md, “Adopted without a question”).
process_start
Process start times for lock owners, read from /proc, ps or 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>/stat field 22, /proc/uptime and the current time; macOS reads ps -o lstart=; Windows reads Get-Process StartTime (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 push that is sent SIGTERM or SIGKILL leaves 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, a git-lfs that 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 /proc and 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 push that 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) with STACKED_PUSH_MARKER set 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’s randomUUID: 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 (isTransactionId in src/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 git ahead 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 git on 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.lock a landing holds, created exclusively, identified by device, inode and filesystem, announced through index~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.json the 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 no preparing.json) as one that stopped before it landed, and removes it. The journal writes preparing.json first, landing-<n>.json before the branch moves and ref-updated.json after 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. An index-installed marker 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>.json record, and reads the real index path from preparing.json. A power loss can take the name of either record, so this module finds the worktree’s own index without preparing.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.rs holds 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 (CommitTransactionRecoveryError in 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.lock files 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.lock is the exact file a transaction created, and installing an index through such a lock. Why: Recovery removes or installs through a real index.lock only when the journal proves the lock is the transaction’s own, by device, inode and filesystem identity, never by path; the incumbent’s commit-transaction-recovery-files.ts, -validation.ts and commit-transaction-install-link.ts define 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.lock the transaction provably created and removes its .keep files, shadow and directory. With a landing record (the caller holds the landing lock) it decides from ref-updated.json or 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.json exists, 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.lock that 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 HEAD only reads the worktree’s HEAD file 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) returns None, 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 from captured.json and 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 the git diff-tree --stdin output 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 for commit-replayed, replay-headers-dropped, and concurrent-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 from git 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 with git 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 to git commit-tree. Why: Replay keeps every byte of the prepared commit except its tree and parent lines, so headers and message stay bytes; a non-UTF-8 encoding commit 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 -p output 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 lost landing.reserveAfterLostRaces races writes reservation-request, takes the reservation lock reservation.lock in 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 shadow HEAD move 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 of git apply --reverse --check for one text file: the subsumption test that asks whether a landed change already sits in the prepared bytes. Why: Git’s apply.c matching (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 in git --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 two git diff-tree runs 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 makes git merge-tree take 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 a RepositoryLocation, a LandingLocation or 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, their include.path files, and the environment’s GIT_CONFIG_COUNT entries. 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 -c entries (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.bare or core.worktree. What: The proof, from files alone, that no configuration scope Git reads for repository discovery sets core.bare or core.worktree. Why: git rev-parse classifies 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 every include and includeIf path 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 None and the caller asks Git.
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 --cached and diff-tree print 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) returns None, 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) and run_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. Setting CLI_GIT_IN_PROCESS_READS=off in 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 --unmerged and the cached diffs only need each entry’s path, mode, object name and stage. The reader returns None for a split index (link), a sparse index (sdir or 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-prefix prints, 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 valid HEAD, the objects and refs directories, files ref storage, a known object format, the owner of every path, no core.bare or core.worktree in any scope), and returns None for 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 sets core.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-file would print for it; a lookup that does not (missing, corrupt, too large, replaced by a refs/replace ref, in a shallow or grafted history) returns None, 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_DELTA and REF_DELTA resolution. Why: Most objects of a real repository are packed. The reader opens the two files, seeks for the few bytes a lookup needs, and returns None for 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 -m and show --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, or None for any command line, repository or configuration it does not fully understand, so a caller tries it first and starts Git on None.
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) and rev-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) is None, 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) is Unsure, 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 show prints 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) returns None so 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-tree and its environment. One git rev-parse prints 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 -C chaining.
rule_add_explicit
Pure decision of the add-explicit policy. What: Pure decision of the add-explicit policy: git add names the paths it stages. Why: git add ., git add -A and git add -u sweep 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 --atomic after git push unless 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 -c and Git’s guess of a remote branch all create a branch in the current worktree. The policy rejects them and points at git 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-HEAD check. What: The pure core of the index-against-HEAD check 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 -o after git commit, or say why not. Why: Every commit must name the paths it includes. Two decisions need a repository fact (sequencer state, index against HEAD); this module asks for the fact by returning a Needs... variant and the caller answers through a resolve_... 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 stash that changes state, a deleting git clean, a git reset that is not --soft, git restore, git checkout and git switch forms that overwrite files, a forced rm, mv, or checkout-index, read-tree --reset and an --abort can 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=false before git status unless the caller set that key. Why: Git’s stock status hints suggest git add <file>... and git 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-unavailable means candidate bytes or a repository fact could not be read; policy-incomplete means 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 rulesFile entries, then the FORBIDDEN_STRINGS_RULES list, else forbidden-strings.local.txt in 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, mode 0700) 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.md records 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_HEAD names nothing. Why: A commit asks git 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_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”). This slice covers the plain scoped commit (git commit -m <message> -- <paths>); 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 return None so native git commit handles them as before. It is on only when the person opts in.
service_commit_args
Which commit command lines the commit service covers, and the message they stand for. What: Which git commit command 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. Every other option (amend, editor, signing, templates, sign-off) 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 with SIGKILL the 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 commit makes them. What: The message and the identities of a commit the service lands, made from the command line the way native git commit makes 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 starting git commit; each rule is compared field by field with the native commit in binary_service_options_tests.rs. Whatever cannot be made without guessing returns nothing and native git commit handles 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_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/user outrank 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 review work 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 ran mise 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 in failed as 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 push and maintenance work 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 config file and the copied repository format. What: The shadow repository’s config file, written by cli-git itself, and the repository format facts it copies from the real common config. Why: Git does not apply core.repositoryformatversion and extensions.* 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 own core.hooksPath); core.worktree, gc.auto and maintenance.auto follow the include, so the real config cannot redirect the worktree or start maintenance in the shadow; hook.<event>.enabled = false for 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 list never shows it and preparation creates no ref in the real repository. Its object store receives the objects native git commit writes, and its alternates name the real object directory, so a real git prune cannot 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 before prepared.json exists. 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 as hooks, info, rr-cache, lfs and modules must 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 private HEAD. Why: Hooks resolve tags, other branches and remote-tracking refs as they stood at invocation. With the files backend the snapshot is one packed-refs file and the private target is a loose ref, which takes precedence over a packed entry; with reftable the snapshot is one update-ref --stdin transaction (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 by git init --bare --template=, a Git start whose whole effect is these few entries; create_shadow_repository overwrites the config it writes and every later shadow step sets the HEAD it writes, so nothing else of Git’s work is used. Creating them here removes one process start from every commit (init; the config read 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 torn git init.
signal_relay
Catching SIGINT, SIGTERM, SIGHUP and SIGQUIT while a Git child runs, and relaying only process-sent signals. What: Catching SIGINT, SIGTERM, SIGHUP and SIGQUIT while 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. SignalGuard owns 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 push passes, 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.md is a tracked file that sessions edit while the service appends to it, and no lock may be taken. One O_APPEND write 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.toml task shadow-review calls fetch with a 30 s signal. This port calls the same endpoint from the wrapper itself, so no node or mise is 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.toml task shadow-review writes them, and their append to the log file. Why: doc/audit/standards-review-gate-shadow.md already 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.toml task shadow-review after 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 --patch text, so the review needs no git diff -U0 start per excerpted file. Why: The script runs git 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 kept line. Why: The root mise.toml task shadow-review builds this body with JSON.stringify and reads the answer with JSON.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.toml task shadow-review (its Luna route; the SHADOW_REVIEW_ALLOW=1 route starts pi and 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, and run_within abandons 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 show for the patch and one git cat-file --batch for the new-side text of the excerpted code files. Why: The root mise.toml task shadow-review starts Git twenty-odd times per landing (rev-parse, log, show, diff-tree, then show and diff -U0 for 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, Number to text, toFixed(1) of a duration, and Git’s %s subject. Why: The root mise.toml task shadow-review measures 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 as ErrorKind::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 of org.freedesktop.systemd1.Manager with 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 (systemctl would 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. Only Reload and StartUnit are 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 in doc/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 sets core.hooksPath. Why: Traditional hooks (executable scripts in .git/hooks or in a core.hooksPath directory) 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 configured core.hooksPath is the only trigger, decided from memory alone. Finding the scripts themselves is the job of git cli-git doctor. The line is a configuration-warning event, 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-unavailable engine 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 runTransactionGit does.
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.rs and transaction_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.ts and commit-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: writeJournalRecord writes JSON.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.ts and commit-transaction-journal-reader.ts do.
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 --exec proceeds 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>:kill ends the process with SIGKILL at the phase, <phase>:kill:<directory> writes <directory>/<phase>.reached first, and <phase>:pause:<directory> writes that marker and waits until <directory>/<phase>.release exists. 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 once owner.json is 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 Ticker and 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 from CLI_GIT_WAIT_NOTICE_SECONDS at 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-0700 directory 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-tree and 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.