1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
use clap::{Args, Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "agentdiff",
about = "Audit and trace AI contributions in git repositories",
version,
propagate_version = true
)]
pub struct Cli {
/// Path to repository (default: current directory)
#[arg(short = 'C', long, global = true)]
pub repo: Option<std::path::PathBuf>,
#[command(subcommand)]
pub command: Command,
}
#[derive(Subcommand, Debug)]
pub enum Command {
/// Configure global agent hooks (Claude, Cursor, Codex, Windsurf, OpenCode, Copilot) — run once per machine
Configure(ConfigureArgs),
/// Initialize agentdiff in this repository (install git hooks, create ledger)
Init(InitArgs),
/// List all captured attribution entries
List(ListArgs),
/// Show line-level attribution for a file (like git-blame)
Blame(BlameArgs),
/// Show aggregate statistics across agents and files
Stats(StatsArgs),
/// Generate CI report (markdown or GitHub annotations)
Report(ReportArgs),
/// Show attribution changes introduced by a commit range
Diff(DiffArgs),
/// Show chronological history of AI contributions
Log(LogArgs),
/// Show one ledger entry by commit SHA
Show(ShowArgs),
/// Ledger maintenance commands
Ledger(LedgerArgs),
/// Fetch refs/notes/agentdiff from origin
SyncNotes,
/// Manage global and repo-level configuration
Config(ConfigArgs),
}
#[derive(Args, Debug)]
pub struct ConfigureArgs {
/// Skip Claude Code hook setup
#[arg(long)]
pub no_claude: bool,
/// Skip Cursor hook setup
#[arg(long)]
pub no_cursor: bool,
/// Skip Codex hook setup
#[arg(long)]
pub no_codex: bool,
/// Skip Gemini/Antigravity hook setup
#[arg(long)]
pub no_antigravity: bool,
/// Skip Windsurf hook setup
#[arg(long)]
pub no_windsurf: bool,
/// Skip OpenCode hook setup
#[arg(long)]
pub no_opencode: bool,
/// Skip VS Code Copilot extension setup
#[arg(long)]
pub no_copilot: bool,
}
#[derive(Args, Debug)]
pub struct InitArgs {
/// Skip git pre-commit and post-commit hook setup
#[arg(long)]
pub no_git_hook: bool,
/// Legacy migration flag (disabled in impl-1)
#[arg(long)]
pub migrate: bool,
}
#[derive(Args, Debug)]
pub struct ListArgs {
/// Show only uncommitted (session) entries
#[arg(long)]
pub uncommitted: bool,
/// Filter by agent name
#[arg(long)]
pub agent: Option<String>,
/// Filter by file path (substring match)
#[arg(long)]
pub file: Option<String>,
/// Limit output to N entries
#[arg(short = 'n', long)]
pub limit: Option<usize>,
}
#[derive(Args, Debug)]
pub struct BlameArgs {
/// File to blame (relative to repo root)
pub file: std::path::PathBuf,
/// Only show lines attributed to a specific agent
#[arg(long)]
pub agent: Option<String>,
}
#[derive(Args, Debug)]
pub struct StatsArgs {
/// Show per-file breakdown
#[arg(long)]
pub by_file: bool,
/// Show per-model breakdown
#[arg(long)]
pub by_model: bool,
/// Only include entries since this ISO timestamp
#[arg(long)]
pub since: Option<String>,
}
#[derive(Args, Debug)]
pub struct ReportArgs {
/// Output format: markdown | annotations | both
#[arg(long, default_value = "markdown")]
pub format: ReportFormat,
/// Write markdown to this file instead of stdout
#[arg(long)]
pub out_md: Option<std::path::PathBuf>,
/// Write annotations JSON to this file instead of stdout
#[arg(long)]
pub out_annotations: Option<std::path::PathBuf>,
/// Only include entries after this ISO timestamp
#[arg(long)]
pub since: Option<String>,
/// Only include entries whose agent name contains this substring
#[arg(long)]
pub agent: Option<String>,
/// Only include entries whose model name contains this substring
#[arg(long)]
pub model: Option<String>,
}
#[derive(Args, Debug)]
pub struct DiffArgs {
/// Commit or range (e.g., HEAD~3, abc123..HEAD). Default: HEAD
pub commit: Option<String>,
/// Show only AI-attributed lines
#[arg(long)]
pub ai_only: bool,
}
#[derive(Args, Debug)]
pub struct LogArgs {
/// Filter by agent
#[arg(long)]
pub agent: Option<String>,
/// Limit to N entries
#[arg(short = 'n', long, default_value = "20")]
pub limit: usize,
/// Show full prompt text
#[arg(long)]
pub full_prompt: bool,
}
#[derive(Args, Debug)]
pub struct ShowArgs {
/// Full SHA or SHA prefix from .agentdiff/ledger.jsonl
pub sha: String,
}
#[derive(Args, Debug)]
pub struct LedgerArgs {
#[command(subcommand)]
pub action: LedgerAction,
}
#[derive(Subcommand, Debug)]
pub enum LedgerAction {
/// Normalize, sort, and deduplicate ledger.jsonl
Repair,
/// Import legacy refs/notes/agentdiff records into ledger.jsonl
ImportNotes,
}
#[derive(Args, Debug)]
pub struct ConfigArgs {
#[command(subcommand)]
pub action: ConfigAction,
}
#[derive(Subcommand, Debug)]
pub enum ConfigAction {
/// Show current config
Show,
/// Set a config value: spillover_dir | scripts_dir | auto_amend_ledger
Set { key: String, value: String },
/// Get a config value
Get { key: String },
/// Add a repo to the config
AddRepo { path: std::path::PathBuf },
}
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum ReportFormat {
Markdown,
Annotations,
Both,
}