Docs / Developer mode
Git
ivralo reads git's record with tools of its own, writes to it only through calls that ask, and pushes exactly one kind of branch: the one it made, for a draft pull request. This page covers the read tools, the write tools and how each may run unattended, the one push, and finding a regression with ivralo bisect.
Who holds what
| Grant | Tools | Held by |
|---|---|---|
code.read | The git reads below | The main agent, the Code Engineer, the Incident Investigator |
git.commit | The git writes below | The main agent and the Code Engineer, in a repository; never an instance |
git.push | open_pull_request | The main agent and the Code Engineer, once the ivralo home holds ivralo's own git identity; never an instance |
The strict profile drops git.commit and git.push. Every tool needs a .git at the workspace root.
The tree line
Every git answer starts with one line about the tree, because a dirty tree changes what a diff or a history means:
tree: clean on main at a1b2c3d (origin/main: 2 ahead, as of the last fetch)
tree: dirty on main at a1b2c3d - 2 modified, 1 staged, 3 untracked
tree: dirty, detached at 9f8e7d6 - 1 conflicted; a merge is in progress
"As of the last fetch" is literal: ivralo never fetches. .ivralo/, .git/ and every .env file are left out of every listing, diff, patch and blame, and the answer says how many it left out.
The reads
Reads ask nobody and run nothing but git: never a diff program or filter your repository configured, and never with a lock on your index.
| Tool | Answers |
|---|---|
git_status | Files that differ from HEAD, by group: conflicted, staged, modified, untracked |
git_diff(path, against, context) | What is not staged, or the tree against a commit: a +/- table first, then the patches |
git_diff_cached(path, context) | What a commit would take |
git_changed_files(base, head) | Every uncommitted file, or the files a branch changed since its merge base |
git_log(limit, path, revision, author, since, grep, term) | Commits newest first; term= finds commits that added or removed a string |
git_log_file(path, limit) | One file's commits across renames |
git_blame(path, start, end) | Runs of lines from one commit, whitespace and moves looked through; 200 lines a call |
git_show_commit(commit, path) | A commit whole: message, table, patches |
git_branch_info() | The branch, its upstream, local branches, an operation in progress |
git_compare_branches(base, head) | The merge base, the commits only on each side, the files changed |
git_merge_conflict_info(path) | A merge, rebase, cherry-pick or revert in progress, and each conflicted file's regions |
Four more answer "why is the code like this" with git's facts, never a judgement:
| Tool | Answers |
|---|---|
git_log_symbol(symbol) | The commits that changed one function's lines, followed back with git log -L |
git_reason_about_change(path, start, end) | The commit that introduced those lines with its whole message, and what changed them since; term= finds when a string was added and removed |
git_related_commits(commit) | Commits git's own wording links to it (a revert, a cherry-pick, a fixup!), then nearby commits on its files |
git_risk_score(path) | Per file over the last thousand commits: commits, recent ones, fixes, authors, co-changed files, with the formula printed |
The writes
Seven tools carry git.commit:
| Tool | Does | Runs as |
|---|---|---|
git_generate_commit_message | Proposes a message for what is staged, in your log's style; writes nothing, asks nothing | |
git_create_branch(name) | A branch at HEAD, switched to | git switch -c |
git_stage_files(paths) | Stages files by path, never a .env | git add |
git_unstage_files(paths) | Unstages files | git restore --staged |
git_commit(message) | Commits what is staged, with your hooks, identity and signing; never amends | git commit |
git_revert_commit(commit) | A new commit that undoes one; a conflict is aborted and nothing changes | git revert |
git_restore_file(path, source) | A file put back as in a commit, its current version kept in ivralo's history first | git restore |
The six that change something show you the call first, like an edit (Editing and approvals). a covers the rest of the turn and /confirm the session. Each is refused before git runs:
- while a changeset is open, because its edits are in the staged tree until it lands;
- in a folder you have not trusted, because a commit runs the repository's hooks;
- in an unattended run, unless one of the three ways below allows it.
Letting a write run unattended
| Way | Example |
|---|---|
Name the tool under allow_tools in a permissions.json | "allow_tools": ["git_stage_files", "git_commit"] |
Allow the git command it runs, as you would for run_shell | "allow": ["git add", "git commit", "git switch -c"] |
Server mode: IVRALO_MODE=server grants git.commit and git.push unattended | written by ivralo init --server |
Each write is judged as the shell command it runs, so a deny entry for that command refuses the tool, attended or not, and the dialog's p stores the command's leading words like it does for a shell command. An entry matches the command as the tool spells it (git add -- <paths>), so the entry is git add, not git add .env. An open changeset and an untrusted folder still refuse, whatever allow says.
There is no tool that fetches, pulls, amends, resets, rebases or deletes a branch, and the shell denies the commands that rewrite history (Shell commands).
On a server install, ivralo init --server writes a git identity of ivralo's own to the home's gitconfig, and git_commit and git_revert_commit use it as author and committer. Your repository's and your global configuration are untouched. Without that file, a commit is yours.
The one push
open_pull_request(branch, title, summary, incident) pushes a branch to origin and opens a draft pull request for it. It asks on every call, and nothing lifts that; with nobody to ask it runs only in server mode. It refuses, saying why, before anything leaves the machine:
- a name that forces (
+branch) or is an option; - the remote's default branch, and
mainwhatever the remote says; - a branch the remote already has: ivralo creates a branch there, it never updates one;
- any commit it would send that this ivralo process did not make with
git_commitin this session, under its own identity, checked against the remote's real heads; - a commit that changes a hook, or uncommitted changes under the hooks;
- an
originthat is not https on the one forge (github.com, or the host ofIVRALO_GITHUB_URL), or a forge host the egress policy refuses; - no forge credential, or a folder you have not trusted.
The token reaches git through one inline credential helper for the forge's host alone, and nothing is stored. It sends no tag and no submodule, and never forces.
ivralo never merges. The shell denies every git push, gh pr merge, gh pr ready and any gh api call naming a merge or ready, whatever permissions.json says. No grant, tool or command merges, approves, enables auto-merge or marks a draft ready. The merge is a person's act on the forge. Draft pull requests covers the server setup.
Finding the commit that broke something
ivralo bisect searches for the first commit where a command fails. The agents do the same with git_find_regression_commit. Either way it runs as a job, never inside a turn and never in your checkout.
ivralo bisect --good v1.2
ivralo bisect --good v1.2 --bad HEAD --command "./scripts/check.sh" --steps 30
| Flag | Default | Meaning |
|---|---|---|
--good | required | A commit where the command passes |
--bad | HEAD | A commit where it fails |
--command | the declared test command | What to run at each commit |
--steps | IVRALO_BISECT_STEPS (20) | The most commands one search runs |
The job checks out each commit in a worktree of its own under the home, runs the command there and reads its exit code as git bisect run does: 0 good, 125 skip, anything else bad. It runs the command at bad and good first; if it does not fail at one and pass at the other, there is nothing to search. Each step is bounded by IVRALO_BISECT_STEP_TIMEOUT (900 seconds). Every verdict is written on the job, so ivralo job <id> shows them and an interrupted job continues from them with ivralo resume <id>. It ends with the first bad commit and its message, or with the few commits it narrowed to. Your branch, index, refs and files are never touched.