- Single-crate Rust CLI (
edition = "2024"— requires Rust 1.85+). Binarysrc/main.rs; lib modules insrc/lib.rs. - Core logic:
src/llm.rs,src/config.rs,src/tool/(8 tools + shared helpers insrc/tool/mod.rs). - Tool descriptions live in
docs/tool/*.txt, compiled in via thetool_description!macro insrc/tool/mod.rs. - LLM prompt templates live in
docs/prompt/*.txt, compiled in via theprompt_description!macro insrc/llm.rs. - Tool structs re-exported from
src/tool/mod.rs:pub use <module>::<Name>. Use ascrate::tool::{Read, List, …}. - Uses
rig-corefor LLM agent/tool orchestration (OpenAI-compatible API).
staged_hash()insrc/tool/mod.rsdoesgit rev-parse --show-toplevel→cd→git write-tree, stored in aOnceLock. Called once at startup (all modes except Help).- Tools that read file content/listings from the frozen tree hash: read, list, grep (via
git ls-tree/git cat-file/git grep <tree>). - Tools that read from the live index (not the tree hash): diff (
git diff --cached), stat (git diff --cached --numstat), glob (git ls-files --cached). They see whatgit addstaged; in practice identical to the tree since the index doesn't change during execution. - log operates on commit history (HEAD), unrelated to the staged snapshot.
- All tool paths are repository-absolute strings starting with
/(e.g./src/main.rs). Enforced by read/list/diff/log. Paths are passed as-is togit ls-tree/git cat-file. - Must be run inside a git repo.
- LLM config read from git config (not env vars), by
src/config.rs:vibe.auth-key(required — CLI errors without it)vibe.base-url(optional; defaults tohttps://api.openai.com/v1)vibe.model-id(optional; defaults togpt-5.2)
- See
gitconfig.examplefor setup. Designed as aprepare-commit-msghook (see README).
Commit mode is a two-step LLM process, not a single call:
llm::commit()— agent with 6 tools (Cache, Diff, Stat, Read, Grep, Glob) produces a structured change summaryllm::message()— plain completion (no tools) combines the summary with the style cache to produce the final message
Before step 1, caches are auto-refreshed if stale (>7 days, see STALENESS_SECS in cache.rs).
| Mode | Flag | LLM calls | Tools available to agent |
|---|---|---|---|
| Commit | (none) | commit() → message() |
Cache, Diff, Stat, Read, Grep, Glob (commit); none (message) |
| Summary | -s |
summarize() + style() |
List, Glob, Grep, Read, Diff, Stat, Log, Cache (summarize); Glob, Grep, Read, Log, Cache (style) |
| Debug tool | -t |
none (local run_tool) |
all 8 (not LLM, just dispatched locally) |
| Translate | -e |
translator() |
none (plain completion) |
- Cache directory:
.git/vibe/inside the repo (not~/.cache/— the README is aspirational). - Files are
.mdwith YAML front matter (---…---).updated_atis auto-injected on write. - Staleness threshold: 7 days. Commit mode auto-refreshes when either
style.mdorproject.mdis stale. - Filenames: must be valid identifiers (no
..,/; only[a-zA-Z0-9_.-]). Capped at 8 KB per file.
- read: default 2000 lines, capped at 50 KB. Use
offset/limitto paginate. - list: capped at 200 entries / 50 KB. No offset/limit — use
globfor narrower lookup. - grep / glob: capped at 100 matches/files.
- diff / stat: capped at 50 KB (no line limit). Stat capped at 200 entries.
- log: default 20 commits, max 100. Capped at 50 KB.
- Truncation notices appended (e.g.
[Showing 1-100 of 523...]).
cargo check # fast compile check (no tests exist)
cargo fmt --check # formatting
cargo run -- -t <tool> '<json>' # debug a single tool
| Flag | Description |
|---|---|
| (none) | Generate commit message from staged changes |
-s |
Refresh project + style caches |
-e "<text>" |
Translate Chinese/mixed text to concise English commit text |
-t <name> '<json>' |
Run a single tool for debugging |
-h |
Show help |
VIBE_DEBUG=1 |
Enable error chain trace in any mode |
- Line truncation at 2000 chars — documented in tool descriptions but not enforced in code.
grepuses--basic-regexp(BRE) but description claims "full regex syntax".parse_grep_lineingrep.rsusesrsplit_once(':')which breaks on file paths containing:.loguses--authorwith default"."(matches all authors) — the regex filter is always applied.