[[command]]
name = "git"
description = "Distributed version control system. git has a huge surface area — hundreds of subcommands and flags, with new ones added regularly. Read-only operations (log, diff, status, blame, show, branch listing) are safe. Write operations (push, commit, merge, rebase, reset, checkout) modify the repo or communicate with remotes. fetch contacts the network but only downloads; it does not modify the working tree. diff and show have 50+ display flags that are all safe but easy to miss. git releases roughly every 2-3 months (e.g. 2.48 through 2.53 shipped between Jan 2025 and Feb 2026), with git 3.0 expected late 2026; flag coverage needs periodic review."
url = "https://git-scm.com/docs"
bare_flags = ["--help", "--version", "-V", "-h"]
examples_safe = [
"git diff src/x.rs",
"git diff main",
"git diff main..feature",
"git diff HEAD~3 -- src/",
"git diff --name-only",
"git -c diff.mnemonicPrefix=false -c diff.noprefix=false diff main -- src/x.rs",
# The pickaxe flags take a SEARCH STRING and a regex, not paths — a path-shaped literal reads
# nothing, and gating the positionals without saying so denied these.
"git diff -S /etc/passwd",
"git diff -G /etc/passwd",
"git diff -O orderfile",
]
examples_denied = [
# `git diff` renders file CONTENT. Given paths outside the tree it is a file reader rather than
# a VCS command, so its positionals are locus-gated — and in the IMPLICIT --no-index form too,
# which git engages on its own when a path points outside the working tree.
"git diff --no-index /etc/shadow /dev/null",
"git diff --no-index ~/.ssh/id_rsa /dev/null",
"git diff /etc/shadow",
# Vetting the `-c` display keys must not launder the subcommand, nor admit an unvetted key.
"git -c diff.noprefix=false push",
"git -c diff.external=/tmp/evil.sh diff",
"git -c diff.orderFile=/etc/shadow diff",
# `-O` names a file git READS; its flag name would never have caught the path-named heuristic.
"git diff -O /etc/shadow",
]
[command.wrapper]
valued = ["-C"]
# push — Phase-1 pilot for per-flag escalation. Base is a vcs-sync (egress to the configured remote
# → network-admin, not auto-approved). Escalating flags ADD a capability: --force/--mirror can
# irreversibly destroy remote refs → remote-destroy-irreversible (yolo). All Denied in the 3-value
# projection today; the point is that the verdict is DERIVED and the flags carry their own research.
# (A future refinement: `-c core.sshCommand=…` is an execution escalator, but it is a valued flag
# matched by VALUE — the simple present/absent mechanism here doesn't cover it yet; the git handler's
# `-c` filter still catches `git -c … push`.)
[[command.sub]]
name = "push"
profile = "vcs-sync"
network_destination = true
destination_flag = "--repo"
fact = "Transfers local commits to a remote repository over the network, updating the remote's refs; sends the committed content to the configured remote. The target may be a configured remote name, an inline URL, or an `ext::<cmd>` transport that runs a local command."
source = "https://git-scm.com/docs/git-push"
judgment = "The send target's provenance is classified onto locus.provenance: a bare remote name is `established`, an inline URL/scp-path is `literal`, a `$VAR` is `opaque`; `ext::<cmd>` runs a local command and worst-cases as RCE."
standalone = ["--help", "-h"]
[[command.sub.flag]]
name = "--force"
classifies = "remote-destroy-irreversible"
fact = "Overwrites the remote ref with no merge check, discarding commits present only on the remote — can irreversibly destroy work not held locally."
source = "https://git-scm.com/docs/git-push — `--force`"
[[command.sub.flag]]
name = "-f"
classifies = "remote-destroy-irreversible"
fact = "Short form of --force."
source = "https://git-scm.com/docs/git-push — `-f`"
[[command.sub.flag]]
name = "--mirror"
classifies = "remote-destroy-irreversible"
fact = "Makes the remote match the local repo exactly, DELETING remote refs absent locally — mass irreversible remote deletion."
source = "https://git-scm.com/docs/git-push — `--mirror`"
# `--receive-pack`/`--exec` name the program git runs on the RECEIVING side — an arbitrary command
# execution vector. Any value is out-of-bounds, so `classifies = "unclassified"` (fail-closed worst).
[[command.sub.flag]]
name = "--receive-pack"
classifies = "unclassified"
fact = "Names the git-receive-pack program run on the remote to receive the push — an arbitrary-program execution surface."
source = "https://git-scm.com/docs/git-push — `--receive-pack`"
[[command.sub.flag]]
name = "--exec"
classifies = "unclassified"
fact = "Alias of --receive-pack: names the program run on the remote."
source = "https://git-scm.com/docs/git-push — `--exec`"
[[command.sub]]
name = "blame"
bare = false
standalone = [
"--color-by-age", "--color-lines",
"--help", "--incremental",
"--line-porcelain",
"--minimal",
"--porcelain", "--progress",
"--root",
"--show-email", "--show-name", "--show-number",
"--show-stats",
"-b", "-c", "-e", "-f", "-h", "-k", "-l", "-n", "-p", "-s", "-t", "-w",
]
valued = [
"--abbrev",
"--contents",
"--date",
"--ignore-rev", "--ignore-revs-file",
"-C", "-L", "-M", "-S",
]
[[command.sub]]
name = "branch"
standalone = [
"--all", "--help", "--ignore-case", "--list",
"--no-abbrev", "--no-color", "--no-column",
"--omit-empty", "--remotes", "--show-current",
"--verbose",
"-a", "-h", "-i", "-l", "-r", "-v", "-vv",
]
valued = [
"--abbrev", "--color", "--column", "--contains",
"--format", "--merged", "--no-contains", "--no-merged",
"--points-at", "--sort",
]
[[command.sub]]
name = "cat-file"
bare = false
standalone = [
"--batch-all-objects", "--buffer",
"--filters", "--follow-symlinks",
"--help", "--mailmap",
"--textconv", "--unordered", "--use-mailmap",
"-Z", "-e", "-h", "-p", "-s", "-t",
]
valued = [
"--batch", "--batch-check", "--batch-command",
"--filter", "--path",
]
[[command.sub]]
name = "check-ignore"
bare = false
standalone = [
"--help", "--no-index", "--non-matching",
"--quiet", "--stdin", "--verbose",
"-h", "-n", "-q", "-v", "-z",
]
[[command.sub]]
name = "config"
standalone = [
"--global", "--local", "--name-only", "--show-origin", "--show-scope",
"--system", "--worktree",
"-z",
]
valued = ["--blob", "--file", "-f"]
[[command.sub.sub]]
name = "--get"
[[command.sub.sub]]
name = "--get-all"
[[command.sub.sub]]
name = "--get-regexp"
[[command.sub.sub]]
name = "--help"
[[command.sub.sub]]
name = "--list"
[[command.sub.sub]]
name = "-h"
[[command.sub.sub]]
name = "-l"
[[command.sub]]
name = "count-objects"
standalone = [
"--help", "--human-readable", "--verbose",
"-H", "-h", "-v",
]
[[command.sub]]
name = "describe"
standalone = [
"--all", "--always", "--contains",
"--debug",
"--exact-match", "--first-parent",
"--help", "--long",
"--tags",
"-h",
]
valued = [
"--abbrev", "--broken",
"--candidates", "--dirty",
"--exclude", "--match",
]
[[command.sub]]
name = "diff"
standalone = [
"--cached", "--cc", "--check", "--color-words",
"--combined-all-paths", "--compact-summary", "--cumulative",
"--dirstat-by-file",
"--exit-code",
"--find-copies", "--find-copies-harder", "--find-renames",
"--first-parent", "--follow", "--full-index",
"--help", "--histogram",
"--ignore-all-space", "--ignore-blank-lines",
"--ignore-cr-at-eol", "--ignore-space-at-eol",
"--ignore-space-change", "--ignore-submodules",
"--merge-base", "--minimal",
"--name-only", "--name-status", "--no-color",
"--no-ext-diff", "--no-index", "--no-patch",
"--no-prefix", "--no-renames", "--no-textconv",
"--numstat",
"--ours",
"--patch", "--patch-with-raw", "--patch-with-stat",
"--patience", "--pickaxe-all",
"--quiet",
"--raw",
"--shortstat", "--staged", "--stat", "--summary",
"--text", "--textconv", "--theirs",
"-B", "-C", "-M", "-R",
"-a", "-b", "-h", "-p", "-q", "-u", "-w", "-z",
]
valued = [
"--abbrev", "--color", "--color-moved",
"--diff-algorithm", "--diff-filter",
"--diff-merges", "--dirstat", "--dst-prefix",
"--inter-hunk-context",
"--line-prefix",
"--output-indicator-new", "--output-indicator-old",
"--relative", "--src-prefix",
"--stat-count", "--stat-graph-width", "--stat-name-width", "--stat-width",
"--submodule",
"--unified", "--word-diff", "--word-diff-regex",
"-G", "-O", "-S", "-U",
]
# `$(git diff --name-only)` is the standard way an agent asks "which files changed", and without a
# claim here the value is unpinnable, so `grep -rn pat $(git diff --name-only)` denies.
#
# Only with `--name-only`: the default output is a PATCH, and --stat/--numstat/--raw/--name-status
# are alternative formats rather than modifiers of it. In-repo, git emits repository-relative paths
# that can be neither absolute nor contain `..`, so the value is worktree-bound.
#
# `--no-index` is the escape hatch and MUST void the claim: it compares two filesystem paths and
# prints them AS GIVEN, so `git diff --no-index --name-only /etc/passwd /tmp/x` yields an absolute
# path from outside any repository. (`locus_from = "operands"` would also worst-case that to
# `system` via the operand itself; the flag is listed so the claim does not depend on it.)
# Verified against git-scm.com/docs/git-diff (git 2.53).
[command.sub.output]
locus_from = "operands"
requires = ["--name-only"]
invalidated_by = [
"--check",
"--compact-summary", "--cumulative",
"--dirstat", "--dirstat-by-file",
"--line-prefix",
"--name-status",
"--no-index",
"--numstat",
"--patch", "--patch-with-raw", "--patch-with-stat",
"--raw",
"--shortstat", "--stat", "--summary",
"-p", "-u", "-z",
]
valued = [
"--abbrev", "--color", "--color-moved",
"--diff-algorithm", "--diff-filter",
"--diff-merges", "--dirstat", "--dst-prefix",
"--inter-hunk-context",
"--line-prefix",
"--output-indicator-new", "--output-indicator-old",
"--relative", "--src-prefix",
"--stat-count", "--stat-graph-width", "--stat-name-width", "--stat-width",
"--submodule",
"--unified", "--word-diff", "--word-diff-regex",
"-G", "-O", "-S", "-U",
]
[[command.sub]]
name = "diff-tree"
bare = false
standalone = [
"--cc", "--combined-all-paths",
"--find-copies-harder", "--full-index",
"--help", "--ignore-all-space", "--ignore-space-at-eol",
"--ignore-space-change",
"--merge-base", "--minimal",
"--name-only", "--name-status", "--no-commit-id",
"--no-ext-diff", "--no-patch", "--no-renames",
"--numstat",
"--patch", "--patch-with-raw", "--patch-with-stat",
"--pickaxe-all",
"--raw", "--root",
"--shortstat", "--stat", "--stdin", "--summary",
"--text",
"-B", "-C", "-M", "-R",
"-a", "-c", "-h", "-m", "-p", "-r", "-s", "-t", "-u", "-v", "-z",
]
valued = [
"--abbrev", "--diff-algorithm", "--diff-filter",
"--pretty",
"-O", "-S",
]
[[command.sub]]
name = "fetch"
standalone = [
"--all", "--append", "--atomic",
"--dry-run",
"--force",
"--help", "--ipv4", "--ipv6",
"--keep",
"--multiple",
"--negotiate-only", "--no-auto-gc", "--no-auto-maintenance",
"--no-show-forced-updates", "--no-tags", "--no-write-fetch-head",
"--porcelain", "--prefetch", "--progress",
"--prune", "--prune-tags",
"--quiet",
"--refetch",
"--set-upstream", "--show-forced-updates", "--stdin",
"--tags",
"--unshallow", "--update-head-ok", "--update-shallow",
"--verbose", "--write-commit-graph", "--write-fetch-head",
"-4", "-6",
"-P", "-a", "-f", "-h", "-k", "-m", "-n", "-p", "-q", "-t", "-u", "-v",
]
valued = [
"--deepen", "--depth",
"--filter",
"--jobs", "--negotiation-tip",
"--recurse-submodules", "--refmap",
"--server-option",
"--shallow-exclude", "--shallow-since",
"-j", "-o",
]
[[command.sub]]
name = "for-each-ref"
standalone = [
"--help", "--ignore-case", "--include-root-refs",
"--omit-empty",
"--perl", "--python",
"--shell", "--stdin",
"--tcl",
"-h", "-p", "-s",
]
valued = [
"--color", "--contains", "--count",
"--exclude", "--format",
"--merged", "--no-contains", "--no-merged",
"--points-at", "--sort",
]
[[command.sub]]
name = "grep"
bare = false
standalone = [
"--all-match", "--and",
"--basic-regexp", "--break",
"--cached", "--column", "--count",
"--exclude-standard", "--extended-regexp",
"--files-with-matches", "--files-without-match",
"--fixed-strings", "--full-name", "--function-context",
"--heading", "--help",
"--ignore-case", "--index", "--invert-match",
"--line-number",
"--name-only", "--no-color", "--no-index", "--null",
"--only-matching",
"--perl-regexp",
"--quiet",
"--recurse-submodules", "--recursive",
"--show-function",
"--text", "--textconv",
"--untracked",
"--word-regexp",
"-E", "-F", "-G", "-H", "-I", "-L", "-P", "-W",
"-a", "-c", "-h", "-i", "-l", "-n", "-o",
"-p", "-q", "-r", "-v", "-w", "-z",
]
valued = [
"--after-context", "--before-context",
"--color", "--context",
"--max-count", "--max-depth",
"--open-files-in-pager", "--threads",
"-A", "-B", "-C", "-O",
"-e", "-f", "-m",
]
[[command.sub]]
name = "help"
tolerate_unknown_short = true
tolerate_unknown_long = true
[[command.sub]]
name = "log"
standalone = [
"--abbrev-commit", "--all", "--ancestry-path",
"--author-date-order", "--bisect", "--boundary",
"--branches", "--cherry", "--cherry-mark", "--cherry-pick",
"--children", "--clear-decorations",
"--compact-summary", "--cumulative",
"--date-order",
"--dense", "--do-walk",
"--early-output",
"--first-parent", "--follow", "--full-diff", "--full-history",
"--graph",
"--help", "--ignore-missing", "--invert-grep",
"--left-only", "--left-right", "--log-size",
"--mailmap",
"--merges", "--minimal",
"--name-only", "--name-status",
"--no-abbrev-commit",
"--no-color", "--no-decorate",
"--no-expand-tabs", "--no-ext-diff", "--no-merges",
"--no-notes", "--no-patch", "--no-prefix",
"--no-renames", "--no-walk",
"--numstat",
"--oneline",
"--parents", "--patch", "--patch-with-raw",
"--patch-with-stat", "--patience", "--pickaxe-all", "--pickaxe-regex",
"--raw", "--reflog", "--regexp-ignore-case", "--relative-date", "--remotes",
"--reverse",
"--shortstat", "--show-linear-break", "--show-notes",
"--show-pulls", "--show-signature", "--simplify-by-decoration",
"--simplify-merges", "--source", "--sparse", "--stat",
"--stdin", "--summary",
"--tags", "--text", "--topo-order",
"--use-mailmap",
"-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9",
"-h", "-i", "-p", "-q", "-s", "-u",
]
valued = [
"--abbrev", "--after", "--author", "--before",
"--color", "--committer", "--date",
"--decorate", "--decorate-refs", "--decorate-refs-exclude",
"--diff-algorithm", "--diff-filter", "--diff-merges",
"--encoding", "--exclude",
"--format", "--glob", "--grep",
"--max-count", "--max-parents", "--min-parents",
"--pretty",
"--since", "--skip", "--until",
"-G", "-L", "-S", "-n",
]
[[command.sub]]
name = "ls-files"
standalone = [
"--cached", "--debug", "--deduplicate", "--deleted",
"--directory", "--empty-directory", "--eol",
"--error-unmatch", "--exclude-standard",
"--full-name",
"--help", "--ignored",
"--killed",
"--modified",
"--no-empty-directory",
"--others",
"--recurse-submodules", "--resolve-undo",
"--sparse", "--stage",
"--unmerged",
"-c", "-d", "-f", "-h", "-i", "-k", "-m", "-o", "-r", "-s", "-t", "-u", "-v", "-z",
]
valued = [
"--abbrev",
"--exclude", "--exclude-from", "--exclude-per-directory",
"--format",
"--with-tree",
"-X", "-x",
]
# `git ls-files` prints one pathname per line, and unlike `git diff` it does so unconditionally —
# there is no mode where the default output stops being paths. That is what makes it declarable
# today, while `git diff --name-only` still cannot be (see docs/design/command-modes.md).
#
# Every path it prints is RELATIVE, so the caller resolves it against the caller's own cwd. That is
# what makes the claim safe under `git -C DIR ls-files`: the wrapper flag is stripped before this
# rule sees the arguments, but redirecting git's directory only changes which names are printed, not
# where the consumer resolves them. `git ls-files ../` is the case that CAN escape the cwd, and it
# escapes through an operand — which is exactly what `operands` bounds.
#
# `invalidated_by` is every flag that stops a line being a bare relative path, confirmed against
# git-scm.com/docs/git-ls-files: -t/-v/-f prefix a status tag, -s/--stage and -u/--unmerged prefix
# mode+object+stage, --debug and --resolve-undo append record data, --eol prefixes three attribute
# columns, --format is arbitrary, --abbrev alters the hash inside the -s form, and -z terminates
# with NUL so a `$( )` splice yields one concatenated word rather than paths. `--full-name` is
# listed too: it prints root-relative rather than cwd-relative names, which does not escape but
# does mean the words no longer denote what this claim says they denote.
[command.sub.output]
locus_from = "operands"
valued = [
"--abbrev", "--exclude", "--exclude-from", "--exclude-per-directory",
"--format", "--with-tree", "-X", "-x",
]
invalidated_by = [
"--abbrev", "--debug", "--eol", "--format", "--full-name",
"--resolve-undo", "--stage", "--unmerged",
"-f", "-s", "-t", "-u", "-v", "-z",
]
[[command.sub]]
name = "ls-remote"
standalone = [
"--branches",
"--exit-code",
"--get-url", "--help",
"--quiet",
"--refs",
"--symref",
"--tags",
"-b", "-h", "-q", "-t",
]
valued = [
"--server-option", "--sort",
"-o",
]
[[command.sub]]
name = "ls-tree"
bare = false
standalone = [
"--full-name", "--full-tree",
"--help", "--long",
"--name-only", "--name-status",
"--object-only",
"-d", "-h", "-l", "-r", "-t", "-z",
]
valued = [
"--abbrev", "--format",
]
[[command.sub]]
name = "merge-base"
bare = false
standalone = [
"--all", "--fork-point",
"--help", "--independent", "--is-ancestor",
"--octopus",
"-a", "-h",
]
[[command.sub]]
name = "merge-tree"
bare = false
standalone = [
"--allow-unrelated-histories",
"--help", "--messages", "--name-only",
"--quiet",
"--stdin", "--trivial-merge", "--write-tree",
"-h", "-z",
]
valued = [
"--merge-base",
"-X",
]
[[command.sub]]
name = "name-rev"
bare = false
standalone = [
"--all", "--always", "--annotate-stdin",
"--help", "--name-only", "--tags", "--undefined",
"-h",
]
valued = [
"--exclude", "--refs",
]
[[command.sub]]
name = "notes"
[[command.sub.sub]]
name = "--help"
[[command.sub.sub]]
name = "-h"
[[command.sub.sub]]
name = "list"
[[command.sub.sub]]
name = "show"
[[command.sub]]
name = "pull"
writes_cwd = "source"
level = "SafeWrite"
standalone = [
"--all", "--allow-unrelated-histories", "--append", "--autostash",
"--commit", "--compact-summary", "--dry-run",
"--edit",
"--ff", "--ff-only", "--force",
"--help",
"--ipv4", "--ipv6",
"--keep",
"--no-autostash", "--no-commit", "--no-edit",
"--no-ff", "--no-gpg-sign", "--no-log",
"--no-rebase", "--no-recurse-submodules",
"--no-show-forced-updates", "--no-signoff",
"--no-squash", "--no-stat", "--no-tags",
"--no-verify", "--no-verify-signatures",
"--progress", "--prune",
"--quiet",
"--set-upstream", "--show-forced-updates",
"--signoff", "--squash", "--stat",
"--tags",
"--verbose", "--verify", "--verify-signatures",
"-4", "-6",
"-a", "-e", "-f", "-h", "-k", "-n", "-p", "-q", "-t", "-v",
]
valued = [
"--cleanup", "--depth", "--deepen",
"--filter",
"--gpg-sign", "--jobs",
"--log",
"--rebase", "--recurse-submodules", "--refmap",
"--server-option", "--shallow-exclude", "--shallow-since",
"--strategy", "--strategy-option",
"--upload-pack",
"-S", "-X", "-j", "-o", "-r", "-s",
]
[[command.sub]]
name = "reflog"
standalone = [
"--abbrev-commit", "--all", "--ancestry-path",
"--author-date-order", "--bisect", "--boundary",
"--branches", "--cherry", "--cherry-mark", "--cherry-pick",
"--children", "--clear-decorations",
"--compact-summary", "--cumulative",
"--date-order",
"--dense", "--do-walk",
"--early-output",
"--first-parent", "--follow", "--full-diff", "--full-history",
"--graph",
"--help", "--ignore-missing", "--invert-grep",
"--left-only", "--left-right", "--log-size",
"--mailmap",
"--merges", "--minimal",
"--name-only", "--name-status",
"--no-abbrev-commit",
"--no-color", "--no-decorate",
"--no-expand-tabs", "--no-ext-diff", "--no-merges",
"--no-notes", "--no-patch", "--no-prefix",
"--no-renames", "--no-walk",
"--numstat",
"--oneline",
"--parents", "--patch", "--patch-with-raw",
"--patch-with-stat", "--patience", "--pickaxe-all", "--pickaxe-regex",
"--raw", "--reflog", "--regexp-ignore-case", "--relative-date", "--remotes",
"--reverse",
"--shortstat", "--show-linear-break", "--show-notes",
"--show-pulls", "--show-signature", "--simplify-by-decoration",
"--simplify-merges", "--source", "--sparse", "--stat",
"--stdin", "--summary",
"--tags", "--text", "--topo-order",
"--use-mailmap",
"-0", "-1", "-2", "-3", "-4", "-5", "-6", "-7", "-8", "-9",
"-h", "-i", "-p", "-q", "-s", "-u",
]
valued = [
"--abbrev", "--after", "--author", "--before",
"--color", "--committer", "--date",
"--decorate", "--decorate-refs", "--decorate-refs-exclude",
"--diff-algorithm", "--diff-filter", "--diff-merges",
"--encoding", "--exclude",
"--format", "--glob", "--grep",
"--max-count", "--max-parents", "--min-parents",
"--pretty",
"--since", "--skip", "--until",
"-G", "-L", "-S", "-n",
]
[[command.sub]]
# `git remote` — READ-ONLY forms only (migrated from the `git_remote` Rust handler to declarative
# subs; it was a pure read/mutate allowlist, no logic). bare / `-v` list configured remotes;
# `get-url` / `show` query them. Mutating subs (add/remove/rename/set-url/set-head/prune/update) deny
# by OMISSION — fail-closed, so a new or aliased mutating sub (`rm`) never slips through.
name = "remote"
nested_bare = true
standalone = ["-v", "--verbose", "-h", "--help"]
[[command.sub.sub]]
name = "get-url"
level = "SafeRead"
max_positional = 1
standalone = ["--push", "--all", "-h", "--help"]
# `git remote show [-n] <name>…` takes MULTIPLE remote names — positionals are harmless remote
# names on a read-only sub, so they are left unbounded (matches the retired handler's any-args form).
[[command.sub.sub]]
name = "show"
level = "SafeRead"
standalone = ["-n", "-h", "--help"]
[[command.sub]]
name = "rev-parse"
bare = false
tolerate_unknown_short = true
standalone = [
"--absolute-git-dir",
"--all",
"--branches",
"--git-common-dir", "--git-dir", "--git-path",
"--help", "--is-bare-repository", "--is-inside-git-dir",
"--is-inside-work-tree", "--is-shallow-repository",
"--local-env-vars",
"--quiet",
"--remotes",
"--shared-index-path", "--show-cdup", "--show-prefix",
"--show-superproject-working-tree", "--show-toplevel",
"--symbolic", "--symbolic-full-name",
"--tags", "--verify",
"-h", "-q",
]
valued = [
"--abbrev-ref", "--after", "--before",
"--default", "--exclude",
"--glob", "--prefix",
"--resolve-git-dir", "--short",
"--since", "--until",
]
[[command.sub]]
name = "shortlog"
standalone = [
"--committer", "--email", "--help", "--numbered", "--summary",
"-c", "-e", "-h", "-n", "-s",
]
valued = [
"--format", "--group",
]
[[command.sub]]
name = "show"
bare = false
standalone = [
"--abbrev-commit", "--cc", "--color-words",
"--combined-all-paths", "--compact-summary", "--cumulative",
"--expand-tabs", "--find-copies", "--find-renames", "--full-index",
"--help", "--histogram",
"--ignore-all-space", "--ignore-blank-lines",
"--ignore-space-at-eol", "--ignore-space-change",
"--mailmap", "--minimal",
"--name-only", "--name-status", "--no-color",
"--no-ext-diff", "--no-notes", "--no-patch",
"--no-prefix", "--no-renames", "--no-textconv",
"--numstat",
"--oneline",
"--patch", "--patch-with-raw", "--patch-with-stat",
"--patience", "--pickaxe-all", "--pickaxe-regex", "--raw",
"--shortstat", "--show-notes", "--show-signature",
"--source", "--stat", "--summary",
"--text", "--textconv", "--use-mailmap",
"-h", "-p", "-q", "-s", "-u", "-w",
]
valued = [
"--abbrev", "--color", "--color-moved",
"--decorate", "--decorate-refs", "--decorate-refs-exclude",
"--diff-algorithm", "--diff-filter", "--diff-merges",
"--encoding", "--format",
"--notes", "--pretty",
"--stat-count", "--stat-graph-width", "--stat-name-width",
"--submodule", "--word-diff", "--word-diff-regex",
"-G", "-O", "-S",
]
[[command.sub]]
name = "stash"
[[command.sub.sub]]
name = "--help"
tolerate_unknown_short = true
tolerate_unknown_long = true
[[command.sub.sub]]
name = "-h"
tolerate_unknown_short = true
tolerate_unknown_long = true
[[command.sub.sub]]
name = "list"
tolerate_unknown_short = true
tolerate_unknown_long = true
[[command.sub.sub]]
name = "show"
tolerate_unknown_short = true
standalone = ["--help", "--patch", "--stat", "-h", "-p", "-u"]
[[command.sub.sub]]
name = "push"
candidate = true
[[command.sub.sub]]
name = "pop"
candidate = true
[[command.sub.sub]]
name = "apply"
candidate = true
[[command.sub.sub]]
name = "drop"
candidate = true
[[command.sub.sub]]
name = "clear"
candidate = true
[[command.sub]]
name = "status"
standalone = [
"--ahead-behind", "--branch",
"--help", "--ignore-submodules",
"--long", "--no-ahead-behind",
"--no-renames", "--null",
"--renames",
"--short", "--show-stash",
"--verbose",
"-b", "-h", "-s", "-v", "-z",
]
valued = [
"--column", "--find-renames",
"--ignored",
"--porcelain",
"--untracked-files",
"-M", "-u",
]
[[command.sub]]
name = "symbolic-ref"
bare = false
max_positional = 1
standalone = [
"--help", "--no-recurse", "--quiet", "--recurse", "--short",
"-h", "-q",
]
[[command.sub]]
name = "tag"
standalone = [
"--help", "--list", "--no-color", "--no-column",
"--verify",
"-h", "-l", "-v",
]
valued = [
"--color", "--column", "--contains",
"--format", "--merged", "--no-contains", "--no-merged",
"--points-at", "--sort", "-n",
]
[[command.sub]]
name = "verify-commit"
bare = false
standalone = [
"--help", "--raw", "--verbose",
"-h", "-v",
]
[[command.sub]]
name = "verify-tag"
bare = false
standalone = [
"--help", "--raw", "--verbose",
"-h", "-v",
]
valued = [
"--format",
]
[[command.sub]]
name = "worktree"
[[command.sub.sub]]
name = "--help"
[[command.sub.sub]]
name = "-h"
[[command.sub.sub]]
name = "list"
standalone = ["--porcelain", "--verbose", "-v", "-z"]
max_positional = 0
[[command.sub]]
name = "cliff"
require_any = ["--no-exec"]
standalone = [
"--bumped-version", "--current",
"--help", "--latest",
"--no-exec",
"--offline",
"--topo-order",
"--unreleased", "--use-branch-tags",
"--verbose", "--version",
"-V", "-h", "-l", "-u", "-v",
]
valued = [
"--body", "--bump",
"--config", "--count-tags",
"--exclude-path",
"--from-context",
"--ignore-tags", "--include-path",
"--repository",
"--skip-commit", "--skip-tags", "--sort", "--strip",
"--tag", "--tag-pattern",
"--with-commit", "--with-tag-message",
"--workdir",
"-b", "-c", "-r", "-s", "-t", "-w",
]
# ═══ Local working-copy operations ═══════════════════════════════════════════════════════════════
# Re-researched 2026-07 against git 2.x under the facet taxonomy. Each declares a `profile`
# (archetypes.toml) rather than a legacy `level`, so the tier is DERIVED from the facets and the
# three-layer provenance below records what upstream documents (`fact`), our inference (`profile`),
# and our stance where the docs don't decide (`judgment`).
#
# On HOOKS: commit/merge/rebase run .git/hooks/* — an `execution` capability, not part of the write
# profile. It is contained by the region model, which freezes writes to `.git` (verified: a write to
# .git/hooks/pre-commit denies), so an agent cannot author what it would run. The hook file itself
# sits at `worktree-trusted`, inside developer's executor band.
[[command.sub]]
name = "switch"
writes_cwd = "source"
profile = "local-mutate-trivial"
fact = "Switches HEAD to an existing branch, updating tracked files to match. git REFUSES to proceed when the switch would overwrite local modifications unless --force/--discard-changes/--merge is given; `git switch -` returns to the previous branch. -c/--create makes a new branch from the current HEAD."
source = "https://git-scm.com/docs/git-switch"
judgment = "The mildest possible worktree mutate: every resulting state is already in the object store, the inverse is one command, and git's own refusal makes data loss impossible — hence `trivial` rather than `recoverable`. The force family is deliberately omitted: --force/--discard-changes exist precisely to defeat that refusal and discard uncommitted work, which is a different (irreversible) capability."
max_positional = 1
standalone = ["--create", "--detach", "--guess", "--help", "--no-guess", "--no-track", "--track", "-c", "-d", "-h", "-t"]
valued = ["--orphan"]
[[command.sub]]
name = "add"
writes_cwd = "source"
profile = "local-mutate-recoverable"
fact = "Stages worktree content into the index. Does not alter working-tree files; the index entry it replaces is recoverable while the prior commit exists, and `git restore --staged` unstages."
source = "https://git-scm.com/docs/git-add"
judgment = "-f/--force only overrides ignore rules (stages a file .gitignore would skip) — it neither deletes nor overwrites working-tree data, so it stays inside the same recoverable profile rather than escalating."
standalone = ["--all", "--dry-run", "--force", "--help", "--ignore-errors", "--intent-to-add", "--no-all", "--refresh", "--renormalize", "--update", "--verbose", "-A", "-N", "-f", "-h", "-n", "-u", "-v"]
valued = ["--chmod", "--pathspec-from-file"]
[[command.sub]]
name = "mv"
profile = "local-mutate-recoverable"
fact = "Moves or renames a tracked file and stages the change; refuses to overwrite an existing destination unless -f/--force is given."
source = "https://git-scm.com/docs/git-mv"
judgment = "The moved content is tracked, so the pre-move state is in the object store — recoverable. -f can clobber an existing destination file, but a TRACKED destination is likewise recoverable from HEAD; the operands are path-gated, so an out-of-worktree destination is caught by locus, not by this profile."
standalone = ["--dry-run", "--force", "--help", "--verbose", "-f", "-h", "-k", "-n", "-v"]
[[command.sub]]
name = "commit"
writes_cwd = "source"
profile = "local-mutate-recoverable"
fact = "Records the staged index as a new commit on the current branch, running the pre-commit, prepare-commit-msg, commit-msg and post-commit hooks. --amend replaces the tip commit; the replaced commit stays reachable through the reflog for the gc grace period."
source = "https://git-scm.com/docs/git-commit"
judgment = "Editor-tier by the level model's own reckoning — its clause names `git commit` as the shape it intends. --amend is a rewrite rather than a plain append, but the superseded commit remains reflog-reachable, so it is recoverable, not destructive. --no-verify is admitted deliberately: it SKIPS hook execution, which narrows rather than widens what runs."
standalone = ["--all", "--allow-empty", "--allow-empty-message", "--amend", "--dry-run", "--help", "--no-edit", "--no-post-rewrite", "--no-verify", "--quiet", "--short", "--signoff", "--verbose", "-a", "-h", "-n", "-q", "-s", "-v"]
valued = ["--author", "--date", "--file", "--message", "--reuse-message", "--squash", "-C", "-F", "-m"]
[[command.sub]]
name = "merge"
writes_cwd = "source"
profile = "local-mutate-recoverable"
fact = "Joins another history into the current branch, creating a merge commit (or fast-forwarding). Runs the pre-merge-commit and post-merge hooks. A conflicted merge leaves markers in the worktree; `git merge --abort` restores the pre-merge state."
source = "https://git-scm.com/docs/git-merge"
judgment = "An ADDITIVE history operation — it creates a commit rather than making one unreachable — so it is a mutate, not a destroy, and lands alongside commit. The pre-merge state is recoverable via --abort or ORIG_HEAD. Left at the same tier as commit deliberately: `git pull` is fetch+merge, and it would be incoherent for the composite to sit below its own component."
standalone = ["--abort", "--continue", "--ff", "--ff-only", "--help", "--no-commit", "--no-edit", "--no-ff", "--no-verify", "--quiet", "--squash", "--stat", "--verbose", "-h", "-q", "-v"]
valued = ["--into-name", "--log", "--message", "--strategy", "--strategy-option", "-X", "-m", "-s"]
[[command.sub]]
name = "rebase"
writes_cwd = "source"
profile = "local-destroy-recoverable"
fact = "Replays commits onto a new base, producing NEW commit objects; the original commits become unreachable from any branch but remain in the reflog (and thus recoverable via ORIG_HEAD / `git rebase --abort`) until gc prunes them. Runs the pre-rebase and post-rewrite hooks."
source = "https://git-scm.com/docs/git-rebase"
judgment = "Classified as a DESTROY rather than a mutate: unlike merge it makes the previous commits unreachable, which is the facet that separates it from commit/merge and lifts it a tier. It is `recoverable` — not irreversible — because the reflog retains the originals, which is exactly the distinction from filter-branch (unbounded scale, and used precisely to make history unrecoverable). --exec/-x is omitted: it runs an arbitrary command per commit and needs delegation, not a blanket allow."
standalone = ["--abort", "--autosquash", "--autostash", "--continue", "--edit-todo", "--fork-point", "--help", "--interactive", "--keep-base", "--no-autosquash", "--no-autostash", "--quiet", "--quit", "--root", "--skip", "--verbose", "-h", "-i", "-q", "-v"]
valued = ["--onto", "--strategy", "--strategy-option", "-X", "-s"]