how does the git hook work in this? what triggers recroding the agent activity, what is recorded and does it use notes or refs?
The system installs itself as the git hooks handler by setting core.hooksPath to point at the git-ai binary. It manages 8+ hooks (pre-commit, post-commit, pre-push, post-rewrite, etc.) and when git invokes any of them, the binary dispatches to the appropriate handler.
The main recording points:
Pre-commit: creates a "checkpoint" capturing the file state and authorship baseline before the commit happens. Each checkpoint records the diff, who authored it (human vs AI agent vs AI tab completion), timestamps, and optionally the conversation transcript.
Post-commit: the heavy lifting. It reads the working log from the parent commit, computes file-level and line-level AI attribution stats, builds an authorship log, and writes it out. It also triggers a background metrics flush.
Post-push: pushes the authorship notes to the remote and flushes metrics.
Post-rewrite / post-merge / post-checkout / etc.: maintain attribution consistency when history gets rewritten (rebase, amend, cherry-pick).
Each checkpoint in .git/ai/working_logs/ stores:
A rewrite log (.git/ai/rewrite_log, JSONL) tracks commit rewrites so attribution survives rebases and amends.
Yes, it uses git notes under refs/notes/ai. The complete AuthorshipLog (serialized JSON) gets attached to each commit as a note. This includes all the line-level attribution data, prompt sessions (with secrets redacted depending on config), file stats, and author info.
Syncing to remotes uses the refspec refs/notes/ai:refs/notes/ai, with tracking refs at refs/notes/ai-remote/<remote-name>. Stash operations use a separate refs/notes/ai-stash ref.
There are three prompt storage modes configurable in post-commit: local (SQLite only, stripped from notes), notes (stored in git notes with secrets redacted), and a default mode that attempts a CAS upload and never keeps full messages in notes.
how does it work out the kind of change?
It's not automatic detection. The IDE extensions tell git-ai what kind of change is happening by calling git-ai checkpoint <preset> --hook-input <json>.
Each supported tool (Claude Code, Cursor, Copilot, etc.) has a preset that interprets a hook_event_name field in the JSON payload. The pattern is the same across all presets: two checkpoints bracket each AI edit.
Before the AI edits — the extension fires a "before" event (PreToolUse for Claude, beforeSubmitPrompt for Cursor, before_edit for tab completions). The preset returns CheckpointKind::Human. This captures the baseline state of files so git-ai knows what the human wrote before the AI touched anything.
After the AI edits — the extension fires an "after" event. The preset returns either CheckpointKind::AiAgent (for conversational tools like Claude Code, Cursor composer) or CheckpointKind::AiTab (for inline tab completions like Copilot tab).
The difference between AiAgent and AiTab is just which preset handles it. The ai_tab preset always returns AiTab and carries no transcript (tab completions aren't conversations). Agent presets return AiAgent and include the full conversation transcript, model name, and session ID.
So the before/after pair is what lets git-ai diff the two snapshots and attribute the lines in between to the right author. Lines that changed between a Human checkpoint and the subsequent AiAgent checkpoint were written by the AI. Lines changed outside that window are human-authored.
Each preset also extracts tool-specific metadata differently — Claude's reads the JSONL transcript file, Cursor's queries its SQLite database, tab completions just pass through the completion ID. But the kind determination is always just a string match on hook_event_name.