gitmotive docs

git blame tells you who. git motive tells you why. This page is generated from the CLI's own help text and the event schema, so it matches the version it ships with.

Install

gitmotive needs Python 3.10 or newer and git. It is pure standard library, with no runtime dependencies, no network calls and no telemetry.

With pipx

pipx install gitmotive

pipx puts gitmotive and git-motive on your PATH. Because git runs git-motive for git motive, both spellings work.

With the install script

curl -fsSL https://gitmotive.tech/install.sh | sh

The script checks for Python 3.10 or newer. If pipx is present it runs pipx install gitmotive. Otherwise it creates a venv at ~/.gitmotive/venv and writes gitmotive and git-motive wrappers to ~/.local/bin. Any arguments after sh -s -- are passed to gitmotive install:

curl -fsSL https://gitmotive.tech/install.sh | sh -s -- --agent claude --scope user

From a clone

git clone https://github.com/rvasqz86/gitmotive
cd gitmotive
sh install.sh

install.sh installs this checkout into ~/.gitmotive/venv and writes the same two wrappers to ~/.local/bin. Arguments are passed to gitmotive install, for example sh install.sh --agent claude,cursor --scope user.

Check the install:

gitmotive --version

Quickstart

  1. Hook up your agent, from the root of a git repository:
gitmotive install --agent claude

This writes the capture hooks to .claude/settings.json, adds a post-commit hook that runs stamp, and adds .gitmotive/ to .git/info/exclude, so nothing gitmotive records is committed.

  1. Use the agent as usual. Every prompt you submit and every file edit it makes is appended to .gitmotive/events.jsonl.
  2. Commit:
git commit -am "add retry with backoff"

The post-commit hook stamps the new edits with the commit they landed in.

  1. Ask any line why it exists:
git motive path/to/file.py:42

gitmotive why path/to/file.py:42 is the same command. It prints the line, the commit from git blame, the edit that wrote it, the prompt behind that edit, the agent's reasoning and the other files changed for the same prompt. When nothing was recorded for a line it says (none recorded) and exits 1. It never guesses.

  1. List what was recorded:
gitmotive log
gitmotive log path/to/file.py

Each prompt is shown with its edit count, the commits its edits landed in and the files it touched.

Agent setup

gitmotive install writes hook entries into each agent's own config file. Pick agents with --agent, as a comma-separated list or all. The default is claude.

Experimental agents record prompts and edits only, because their hook payloads are not fully documented yet. gitmotive install prints a note for each one.

Repo scope and user scope

--scope repo is the default. Hook configs are written inside the repository, events go to <repo>/.gitmotive/events.jsonl, and a post-commit hook stamps edits with their commit.

--scope user writes nothing into any repository. Hook configs go under your home directory, the hook commands are prefixed with GITMOTIVE_STORE=user, and events go to ~/.gitmotive/repos/<hash>/events.jsonl, one directory per repository. Set GITMOTIVE_STORE=user in your shell too, so why and log read the same store. There is no git hook in user scope: why and log attach edits to commits when they read them.

Where the hooks go

Hook commands

Each hook runs a command of this form, where <run> is how the hook starts gitmotive:

claude : <run> capture
cursor : <run> capture --agent cursor --event <beforeSubmitPrompt|afterFileEdit>
copilot: <run> capture --agent claude --source copilot
omp    : <run> capture --agent claude --source omp

When gitmotive is installed into a site-packages directory (pipx, a venv, the install script), <run> is <python> -I -m gitmotive, with the absolute path of that Python. -I keeps a hook whose working directory is inside a gitmotive checkout from importing the checkout instead of the installed copy. When gitmotive runs from a source tree, <run> is PYTHONPATH=<pkg> <python> -m gitmotive. The post-commit stamp line uses the same <run>. In user scope every command starts with GITMOTIVE_STORE=user.

Copilot and oh-my-pi send Claude-shaped payloads, so they are parsed with --agent claude. --source records which product actually produced the event.

After a Python or pipx change

The hook commands contain an absolute Python path. After you upgrade Python, reinstall with pipx, or move the venv, run gitmotive install again with the same --agent and --scope. It upserts: each existing gitmotive hook command and the post-commit stamp line are replaced in place, so stale paths heal and there is still exactly one gitmotive entry per hook event. Your other hooks are left alone.

CLI reference

Generated from the CLI itself. This is what gitmotive help prints:

git blame tells you who. git motive tells you why.

  git motive <file>:<line>        why does this line exist (also: gitmotive why
                                  <file>:<line>)
  gitmotive log [file]            prompts recorded, with files they touched
  gitmotive stamp                 tag uncommitted edit events with HEAD (run
                                  from post-commit)
  gitmotive install [--agent claude|cursor|copilot|omp|all] [--scope repo|user]
                                  copilot (experimental), omp (experimental).
                                  repo: hooks in this repo. user: hooks under
                                  ~/, zero repo footprint
  gitmotive capture [--agent A] [--event E] [--source S]
                                  (internal) hook entrypoint, reads payload on
                                  stdin. --source S labels the product (e.g.
                                  copilot, omp) over the payload agent
  gitmotive help [cmd] | -h | --help
                                  show this help, or one command's entry.
                                  gitmotive --version | -V prints the version

Experimental agents: copilot, omp (experimental). They record prompts and edits only; their hook payloads are not fully documented.

git motive / gitmotive why

git motive <file>:<line>

why does this line exist (also: gitmotive why <file>:<line>)

gitmotive log

gitmotive log [file]

prompts recorded, with files they touched

gitmotive stamp

gitmotive stamp

tag uncommitted edit events with HEAD (run from post-commit)

gitmotive install

gitmotive install [--agent claude|cursor|copilot|omp|all] [--scope repo|user]

copilot (experimental), omp (experimental). repo: hooks in this repo. user: hooks under ~/, zero repo footprint

gitmotive capture

gitmotive capture [--agent A] [--event E] [--source S]

(internal) hook entrypoint, reads payload on stdin. --source S labels the product (e.g. copilot, omp) over the payload agent

gitmotive help

gitmotive help [cmd] | -h | --help

show this help, or one command's entry. gitmotive --version | -V prints the version

gitmotive --version

gitmotive --version | -V

print the version and exit

Data format

Everything gitmotive records is one file, events.jsonl, with one JSON object per line. Each object is an event: a prompt you submitted, or an edit the agent made to a file.

Store locations

The store is found in this order:

  1. GITMOTIVE_STORE=user is set: ~/.gitmotive/repos/<hash>/events.jsonl, where <hash> is derived from the repository's absolute path.
  2. <repo>/.gitmotive/events.jsonl exists: use it.
  3. ~/.gitmotive/repos/<hash>/events.jsonl exists: use it.
  4. Otherwise <repo>/.gitmotive/events.jsonl.

GITMOTIVE_HOME moves ~/.gitmotive somewhere else. gitmotive install adds .gitmotive/ to .git/info/exclude, so a repo-scope store (<repo>/.gitmotive/) is never committed. The entry applies to repo scope only; the user-scope store lives outside the repository.

Append-only

Hooks only ever append a line, so concurrent hook runs never corrupt the file. The one exception is stamping: gitmotive stamp, why and log fill in the commit field of edits that have landed in a commit. They write a complete new file and atomically replace the old one. No event is ever removed.

The agent field

agent is the product that produced the event (claude, cursor, copilot or omp), not the payload shape. Copilot CLI and oh-my-pi send Claude-shaped payloads but are recorded as copilot and omp. Events captured by older versions have no agent or model.

Event fields

Unknown keys are ignored on read and missing optional keys take their defaults; lines without kind, session or ts are skipped. Older and newer files stay readable.

FieldTypeMeaning
kindstrevent type: prompt or edit
sessionstragent session or conversation id
tsfloatunix timestamp when the event was captured
idstrunique event id
promptOptional[str]user prompt text (prompt events)
fileOptional[str]edited file, relative to the repo root (edit events)
toolOptional[str]tool that performed the edit
oldOptional[str]replaced text, if any
newOptional[str]new text, or full content for whole-file writes
reasoningOptional[str]assistant text immediately preceding the edit
prompt_idOptional[str]id of the prompt event this edit belongs to
commitOptional[str]commit the edit landed in, stamped after commit
agentOptional[str]product that produced the event (claude, cursor, copilot, omp)
modelOptional[str]model that produced the event, when known
rawOptional[dict]original hook payload, for forward compatibility