# Contributing to agentdiff
Thanks for your interest in contributing. This guide covers bug reports, development setup, adding agent support, and the PR process.
## Quick links
- [Report a bug](#reporting-bugs)
- [Development setup](#development-setup)
- [Add a new agent](#adding-a-new-agent)
- [PR process](#pull-request-process)
---
## Reporting Bugs
Open an issue at [github.com/codeprakhar25/agentdiff/issues](https://github.com/codeprakhar25/agentdiff/issues).
Include:
- `agentdiff --version`
- OS, Rust version (`rustc --version`), Python version (`python3 --version`)
- Which agent you were using
- Steps to reproduce
- What you expected vs. what happened
- Debug logs (see below)
**Capture debug logs:**
```bash
export AGENTDIFF_DEBUG=1
# reproduce the issue (make an AI edit, commit)
cat ~/.agentdiff/logs/capture-<agent>.log
cat .git/agentdiff/session.jsonl
```
---
## Development Setup
**Prerequisites:** Rust 1.85+, Python 3.7+, Git 2.20+
```bash
git clone https://github.com/codeprakhar25/agentdiff.git
cd agentdiff
cargo build
```
### Running tests
```bash
# Rust tests
cargo test
# Python unit tests (capture script logic)
python3 -m unittest discover -s scripts/tests -v
# MCP server smoke test
python3 scripts/mcp-smoke-test.py
# Full end-to-end test (all 7 agents, simulated payloads)
bash scripts/e2e-test.sh
```
### Installing locally for manual testing
```bash
cargo install --path . --force
# Run configure + init in a throwaway repo
agentdiff configure
mkdir /tmp/test-repo && cd /tmp/test-repo
git init && git config user.email "test@test.com" && git config user.name "Test"
agentdiff init
```
---
## Project Structure
```
src/
├── main.rs ← CLI entry point and command routing
├── cli.rs ← Argument definitions (clap)
├── init.rs ← agentdiff configure + agentdiff init logic
├── config.rs ← Global config (load, save, paths)
├── store.rs ← Ledger + session reading
├── data.rs ← Data structures (Entry, LedgerRecord)
├── util.rs ← Shared helpers
├── bin/
│ └── agentdiff-mcp.rs ← MCP stdio server
└── commands/ ← One file per CLI command
scripts/
├── capture-<agent>.py ← Agent-specific capture scripts (stdin JSON → session.jsonl)
├── prepare-ledger.py ← Pre-commit: match staged diff to captured events
├── finalize-ledger.py ← Post-commit: write ledger entry
├── record-context.py ← MCP record_context tool handler
├── opencode-agentdiff.ts ← OpenCode TypeScript plugin template
├── vscode-extension/ ← Copilot VS Code extension (plain JS)
└── tests/ ← Python unit tests
```
---
## Adding a New Agent
Supporting a new AI agent means two things: a capture script and a hook installation step.
### 1. Write the capture script
Create `scripts/capture-<agentname>.py`. The script:
- Reads a JSON payload from **stdin**
- Resolves the repo root with `git rev-parse --show-toplevel`
- Writes one JSON entry to `<repo>/.git/agentdiff/session.jsonl`
Minimum entry schema:
```json
{
"timestamp": "2026-03-28T10:00:00Z",
"agent": "my-agent",
"model": "model-name-or-unknown",
"session_id": "session-id-or-unknown",
"tool": "Edit",
"file": "src/main.rs",
"abs_file": "/home/user/project/src/main.rs",
"lines": [10, 11, 12],
"prompt": "prompt text or null"
}
```
Look at `scripts/capture-claude.py` as the reference implementation — it handles path normalization, repo resolution, and debug logging.
**Debug logging** — use the shared pattern:
```python
import os, sys
def debug_log(msg):
if os.environ.get("AGENTDIFF_DEBUG"):
log_dir = Path.home() / ".agentdiff" / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
with open(log_dir / "capture-myagent.log", "a") as f:
f.write(f"{datetime.utcnow().isoformat()} {msg}\n")
```
### 2. Embed the script in the binary
In `src/init.rs`, add:
```rust
const MYAGENT_CAPTURE_SCRIPT: &str = include_str!("../scripts/capture-myagent.py");
```
Add it to `step_install_scripts()`:
```rust
("capture-myagent.py", MYAGENT_CAPTURE_SCRIPT),
```
### 3. Add the hook installation step
Add a `step_configure_myagent()` function that writes to the agent's global config file (home directory preferred over per-repo). Call it from `run_configure()`.
### 4. Add CLI flags
In `src/cli.rs`, add to `ConfigureArgs`:
```rust
/// Skip MyAgent hook setup
#[arg(long)]
pub no_myagent: bool,
```
Wire it through `main.rs` and `run_configure()`.
### 5. Add tests
Add a Python unit test in `scripts/tests/test_capture_myagent.py` that:
- Pipes a minimal valid JSON payload to the script
- Asserts the resulting `session.jsonl` entry has the correct `agent`, `tool`, and `file` fields
Add an entry to the `e2e-test.sh` script simulating a real payload.
---
## Code Conventions
### Rust
- Follow existing patterns — match the style of adjacent code
- Use `anyhow::Result` for error propagation; add `.context("...")` on file I/O
- Prefer `dirs::home_dir()` over hardcoding `~`
- All user-visible output uses `colored` crate: `"ok".green()`, `"!".yellow()`, `"--".dimmed()`
- No `unwrap()` in non-test code except after `dirs::home_dir()` (it won't fail on supported platforms)
### Python (capture scripts)
- Target Python 3.7+ — no f-string `=` syntax, no `match` statements
- Read from `sys.stdin`; resolve paths with `pathlib.Path`
- Write to `session.jsonl` with a file lock (`fcntl.flock` / `msvcrt.locking`)
- Fail silently on errors that shouldn't break git workflows — log to debug file, `sys.exit(0)`
- Always resolve repo root with `git rev-parse --show-toplevel` from the file's directory
### Commit messages
Use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat: add MyAgent capture support
fix: handle missing cwd in windsurf payload
docs: add CI integration example
chore: bump serde to 1.0.200
```
---
## Pull Request Process
1. **Fork** the repo and branch from `master`:
```bash
git checkout -b feat/my-agent-support
```
2. **Make focused changes** — one feature or fix per PR.
3. **Build and test:**
```bash
cargo build
cargo test
python3 -m unittest discover -s scripts/tests
bash scripts/e2e-test.sh
```
4. **Open a PR** with:
- What changed and why
- How to test it
- `Closes #<issue>` if applicable
5. **Address review feedback** — push updates and reply to comments.
### CI checks
All PRs must pass:
- `cargo build --locked` — no compile errors
- `cargo test --locked` — all Rust tests green
- `python3 -m unittest discover -s scripts/tests` — all Python tests green
- `python3 scripts/mcp-smoke-test.py` — MCP server smoke test
---
## License
By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE-MIT) and [Apache-2.0 License](LICENSE-APACHE).