Expand description
The policy engine: the registry, triggers, events, the passes over candidates and direct fixes. What: The policy engine: the registry, triggers, events, the passes over candidates and direct fixes. Why: Policies are the reason the wrapper exists, so the engine and its shared pieces have one home.
In TS you’d write (pseudocode):
// policy/index.ts: one `export * from './file'` per module below.Modules§
- diagnostics
- JSONL event rendering for wrapper-made diagnostics. What: Render wrapper diagnostics as the JSONL events callers already parse. Why: Agents and hooks read cli-git’s event stream line by line; every wrapper-made event must be one compact JSON object per line with stable field order.
- direct_
fix - The direct fix:
converge corrections in memory,
then install them.
What:
git cli-git fix: run policy passes over the selected files, apply the corrections they propose in memory until a pass proposes none, then install the corrected files into the worktree when that last pass is clean. Why: The installed wrapper’sdirect-fix.tsanddirect-fix-convergence.tsdecide the behaviour: only the last pass is reported, a fix summary follows it only when a file changed, nothing reaches the worktree unless the last pass exits 0, and a fix that cycles or keeps changing ends with one engine failure. The bounded loop itself ispolicy_convergence::converge; this module supplies its passes. Corrections replace whole file contents in memory instead of being staged into the private index, so a pass starts no Git process for them. - direct_
fix_ install - Installing a direct fix’s corrections into the worktree,
all or none.
What:
Install the corrections a converged
git cli-git fixsettled on into the worktree, all of them or none, and never touch the index. Why: The installed wrapper’sdirect-fix-install.tsdecides this protocol: every corrected file must still hold the bytes the fix read, each replacement is written beside its file and renamed over it, a copy of the old file is kept until the end, and the real index must hold the same bytes afterwards. Any failure restores every file already replaced. Failure messages name no file, because a pathname can hold the text a scan exists to keep out of output. - event_
hints - The intent-guessing hint that follows a finding, an engine failure or a notice. What: The hint that follows a finding, an engine failure or a warn-unsafe notice: a guess at what the person was trying to do, and the concrete next step. Why: A block that only states a rule leaves the next move to guesswork. Every block and every divergence from real Git’s behavior names the likely intent and a way forward, so a rejected command is a step toward the goal. Messages built with their own hint (the discard, branch, add, root and commit-only rejections) are listed as self-hinted, and a guard test fails when a code has neither.
- event_
path - A pathname as events report it:
text,
plus base64
pathByteswhen it is not UTF-8. What: A repository path as an event reports it: readable text, and the exact bytes as well when those bytes are not UTF-8. Why: Git allows any bytes in a file name, and a JSON string can hold only text. An event keepspathas text with replacement characters, so every existing reader works unchanged, and adds an optionalpathBytesfield, base64 encoded, only for a name that is not UTF-8, so a reader that needs the file can still find it. This is option C ofdoc/planning/cli-git-rust-open-decisions.md, section “Non-UTF-8 paths in events and journals”, which the owner adopted. - 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. - policy_
catalog_ specifiers - The optional catalog-specifier policy over the shared rule crate:
every dependency in a tracked
package.jsonis a catalog or workspace reference. What: The optionalmono/catalog-specifierspolicy: every dependency in a trackedpackage.jsoniscatalog:,catalog:<name>or exactlyworkspace:*, and nopackage.json5,package.yamlorpackage.ymlis tracked. Why:pnpm installaccepts a literal version without a word, so the repository checks the rule itself (doc/handover/catalog-specifier-policy.md). The rule, its findings and their wording belong to themonochromatic-catalog-specifiercrate, which the pnpm wrapper shares; this module only hands it each candidate manifest’s path and its candidate bytes, never the worktree’s, and turns its findings into the engine’s. Only the last path segment decides what a file is, and it is decided on the path’s raw bytes, so a manifest below a directory whose name is not UTF-8 is still checked. - policy_
checks - The shipped policies as checks over rule cores and repository facts. What: The shipped policies as the engine sees them: one check per policy, built from the pure rule cores and the repository facts they ask for. Why: The engine owns order, severity and stopping; the rule cores own decisions from arguments; this module joins them and fetches a repository fact only when a core asks. The content policies read the lifecycle’s candidates: they report nothing where a lifecycle has none. The Markdown policy checks the candidates it is given with the native Markdown linter.
- policy_
content - What a lifecycle offers content policies,
and its candidates once prepared.
What:
What one lifecycle offers the content policies,
and the candidates it prepared
for them,
read once and shared by every content policy of the invocation.
Why:
A forwarded command other than
git addhas no candidates, so its content policies find nothing.git addand the direct commands have candidates, which are prepared the first time a content policy reads them: an invocation whose content policies are all off or escaped starts no Git process for them. A failure to prepare is remembered, so it is reported once and never retried. - policy_
convergence - The bounded loop that repeats a pass after corrections changed candidate content. What: The bounded loop that repeats a whole policy pass after corrections changed the candidate content, until the content is stable or the loop gives up. Why: A correction by one policy can create a finding for another, so every changed state restarts the complete order. The loop must end: at most eight passes may change the content, and returning to an earlier state is a cycle. This module owns only that control flow; running policies, applying corrections and storing exact states stay behind a small interface, so the loop is testable without Git.
- policy_
engine - Ordered policy execution for one lifecycle point. What: Run shipped policies in registry order for one lifecycle point and settle the outcome: which events were produced and whether the command may proceed. Why: Every lifecycle shares one execution contract: a policy runs only for the triggers it declares, at the configured severity, unless it is filtered out, escaped or off; the first error finding stops the pass unless the caller asked to keep going; and a policy or lifecycle this executable cannot evaluate is reported as unavailable, never as clean.
- policy_
events - Policy events and their JSON Lines rendering.
What:
The events one policy pass produces,
and their JSON Lines rendering.
Why:
Agents and scripts read these lines from wrapper standard error and from
git cli-git check|fixstandard output. Each event is one compact JSON object with the incumbent’s field names and field order, so existing readers parse native events unchanged. Events are numbered when rendered, so a list of events always prints with consecutive numbers in emission order. - policy_
final_ newline - The built-in final-newline policy:
canonical final LF,
preserved paths,
findings.
What:
The built-in
final-newlinepolicy: a non-empty UTF-8 text file without NUL bytes must end with exactly one LF byte. Why: The rule, its finding and its generated-output path families are the installed wrapper’s (final-newline-policy.ts,final-newline-normalize.ts), so the native wrapper reports the same files with the same words. A file whosetextGit attribute is unset (-text, whichbinaryincludes) keeps its exact bytes, which the installed wrapper could only do by path; the attribute travels with a package that moves, where a path list goes stale. The normalized bytes are also the full-content correction a fixing lifecycle applies. - policy_
forbidden_ strings - The optional forbidden-strings policy over the linked scanner,
with redacted findings.
What:
The optional
security/forbidden-stringspolicy: scan every candidate’s exact bytes and pathname through the linked scanner and report each match, redacted. Why: The installed wrapper wrote candidate bytes to temporary files, started the scanner executable and parsed its standard error. Here the same scanner is a library: rules are loaded once per invocation, only when a candidate can be scanned, and each finding carries the scanner’s masked display path and an opaque rule name, never the matched text. A scan that could not complete ends the policy with the code of its cause, never as a clean result. - policy_
pass - One complete pass:
built-in policies,
fixed transforms,
optional policies.
What:
One complete policy pass over the shipped policies:
the built-in policies,
then
the fixed argument transforms of a forwarded command,
then the optional policies.
Why:
Every lifecycle runs the same three stages in the same order,
and each stage can
end the pass.
Keeping the sequence in one place means a wrapped command and a
direct
git cli-git checkcannot drift apart in what they run or when they stop. - policy_
registry - The fixed registry of shipped policies. What: The fixed registry of policies compiled into the native wrapper. Why: Repository JSONC can only tune shipped policies; it can never name code to run.
- policy_
root_ context - The optional root-context policy:
no top-level
CONTEXT.mdenters the index. What: The optionalmono/forbidden-root-contextpolicy: aCONTEXT.mdfile at the repository’s top level must not enter the index or be checked. Why: This repository keeps no context files; agents read the source on every probe (doc/agent/domain.md). The rule and its finding are the installed wrapper’s (package/git-policy/repository/src/index.ts): only the top-level pathname counts, and removing the file is always allowed. - policy_
transforms - The fixed argument transforms of a forwarded command,
in order.
What:
The fixed argument transforms of a forwarded command,
in their fixed order:
atomic push,
commit-only,
status hints off.
Why:
These are not policies:
they have no severity and cannot be configured.
They add
--atomictogit push,-otogit commitand-c advice.statusHints=falsebeforegit status, and the commit-only transform can reject a commit. The decisions belong to the pure rule cores; this module runs them in order and fetches the two repository facts commit-only may ask for. - policy_
trigger - Lifecycle triggers and the trigger set of every shipped policy. What: The lifecycle points at which policies run, which shipped policy runs at which point, and which points this executable can serve. Why: A policy runs only for the triggers it declares. A trigger whose lifecycle is not ported must be answered as “unavailable”, never as an empty result that would read as “checked and clean”.
- 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.