agentdiff 0.1.30

Audit and trace autonomous AI code contributions in git repositories
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
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 agent context for a file
    Context(ContextArgs),

    /// Aggregate report in text, markdown, annotations, or JSONL format
    Report(ReportArgs),

    /// Show attribution changes introduced by a commit range
    Diff(DiffArgs),

    /// Show one trace entry by UUID or commit SHA
    Show(ShowArgs),

    /// Manage global and repo-level configuration
    Config(ConfigArgs),

    /// Manage local signing keypair
    Keys(KeysArgs),

    /// Verify ledger entry signatures
    Verify(VerifyArgs),

    /// Evaluate agentdiff policy rules
    Policy(PolicyArgs),

    /// Show current agentdiff health (hooks, keys, traces)
    Status(StatusArgs),

    /// Push local traces to per-branch ref on origin
    Push(PushArgs),

    /// Consolidate per-branch ref traces into agentdiff-meta (used by CI)
    Consolidate(ConsolidateArgs),

    /// Write agentdiff CI workflow files to .github/workflows/
    InstallCi(InstallCiArgs),

    /// Install the AgentDiff context skill for Cursor agents
    InstallSkill(InstallSkillArgs),

    /// [internal] Sign the last trace entry — called by the post-commit hook
    #[command(hide = true)]
    SignEntry,
}

#[derive(Args, Debug)]
pub struct StatusArgs {
    /// Show remote agentdiff ref state (refs/agentdiff/* on origin)
    #[arg(long)]
    pub remote: bool,

    /// With --remote: skip fetching trace counts for each ref (faster)
    #[arg(long)]
    pub no_fetch: bool,

    /// With --remote: only show developers active within this window (e.g. 7, 7d, 48h)
    #[arg(long, value_name = "DURATION")]
    pub since: Option<String>,

    /// Print a single-line attribution summary for HEAD (used by the post-commit hook)
    #[arg(long, hide = true)]
    pub oneline: bool,
}

#[derive(Args, Debug)]
pub struct ConfigureArgs {
    /// Configure every supported agent without prompting
    #[arg(long)]
    pub all: bool,

    /// Configure only these agents (comma-separated: claude-code,cursor,codex,windsurf,opencode,copilot,antigravity)
    #[arg(long, value_delimiter = ',', value_name = "AGENTS")]
    pub agents: Vec<String>,

    /// 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,

    /// Skip MCP server registration with Claude Code
    #[arg(long)]
    pub no_mcp: bool,

    /// Skip writing/updating the AgentDiff section in AGENTS.md
    #[arg(long)]
    pub no_agents_md: bool,

    /// Print the full per-step setup log (quiet by default)
    #[arg(short, long)]
    pub verbose: bool,
}

#[derive(Args, Debug)]
pub struct InitArgs {
    /// Skip git pre-commit and post-commit hook setup
    #[arg(long)]
    pub no_git_hook: bool,

    /// Print the full per-step setup log (quiet by default)
    #[arg(short, long)]
    pub verbose: 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 (or N commits with --by-commit)
    #[arg(short = 'n', long)]
    pub limit: Option<usize>,

    /// Group entries by commit and display chronologically (replaces `log`)
    #[arg(long)]
    pub by_commit: bool,

    /// With --by-commit: show full prompt text
    #[arg(long)]
    pub full_prompt: bool,
}

#[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 ContextArgs {
    /// File to explain (relative to repo root)
    pub file: std::path::PathBuf,

    /// Output machine-readable JSON
    #[arg(long)]
    pub json: bool,

    /// Only include traces whose agent name contains this substring
    #[arg(long)]
    pub agent: Option<String>,

    /// Limit number of trace records shown
    #[arg(short = 'n', long, default_value_t = 10)]
    pub limit: usize,
}

#[derive(Args, Debug)]
pub struct ReportArgs {
    /// Output format: text (default, terminal-friendly) | markdown | annotations | jsonl | json
    #[arg(long, default_value = "text")]
    pub format: ReportFormat,

    /// Write output to a file instead of stdout
    #[arg(long)]
    pub out: Option<std::path::PathBuf>,

    /// Post the markdown report as a PR comment (requires gh CLI and GH_TOKEN).
    /// Provide PR number, or omit to auto-detect from the current branch.
    #[arg(long, value_name = "PR_NUMBER")]
    pub post_pr_comment: Option<Option<u64>>,

    /// 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>,

    /// Include structured intent/files-read context in markdown reports (JSON is always structured)
    #[arg(long)]
    pub context: bool,

    /// With --format=text: also show per-file breakdown
    #[arg(long)]
    pub by_file: bool,

    /// With --format=text: also show per-model breakdown
    #[arg(long)]
    pub by_model: bool,
}

#[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 ShowArgs {
    /// UUID or commit SHA prefix to look up
    pub sha: String,
}

#[derive(Args, Debug)]
pub struct PushArgs {
    /// Branch to push traces for (default: current branch)
    #[arg(long)]
    pub branch: Option<String>,

    /// Suppress output
    #[arg(long)]
    pub quiet: bool,
}

#[derive(Args, Debug)]
pub struct ConsolidateArgs {
    /// Branch whose traces to consolidate into agentdiff-meta
    #[arg(long)]
    pub branch: Option<String>,

    /// Also push agentdiff-meta and delete the remote per-branch ref
    #[arg(long)]
    pub push: bool,
}

#[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: scripts_dir | capture_prompts
    Set { key: String, value: String },
    /// Get a config value
    Get { key: String },
}

#[derive(Debug, Clone, clap::ValueEnum)]
pub enum ReportFormat {
    /// Terminal display (replaces `stats`)
    Text,
    /// Markdown report
    Markdown,
    /// GitHub check annotations JSON
    Annotations,
    /// Agent Trace JSONL (replaces `export`)
    Jsonl,
    /// Structured JSON summary
    Json,
}

// ── Keys ─────────────────────────────────────────────────────────────────────

#[derive(Args, Debug)]
pub struct KeysArgs {
    #[command(subcommand)]
    pub action: KeysAction,
}

#[derive(Subcommand, Debug)]
pub enum KeysAction {
    /// Generate a new local ed25519 signing keypair
    Init,
    /// Register the local public key in the git key registry (refs/agentdiff/keys/)
    Register,
    /// Rotate the local keypair: archive the old keys, generate new ones, and register them
    Rotate(RotateKeysArgs),
}

#[derive(Args, Debug, Default)]
pub struct RotateKeysArgs {
    /// Re-sign the last N entries in the current branch's local trace buffer with the new key
    #[arg(long)]
    pub resign_last: Option<usize>,
}

// ── Verify ────────────────────────────────────────────────────────────────────

#[derive(Args, Debug)]
pub struct VerifyArgs {
    /// Verify only entries after this commit SHA (default: git merge-base with main)
    #[arg(long)]
    pub since: Option<String>,

    /// Treat missing signatures as hard failures (exit 1 immediately)
    #[arg(long)]
    pub strict: bool,
}

// ── Policy ────────────────────────────────────────────────────────────────────

#[derive(Args, Debug)]
pub struct PolicyArgs {
    #[command(subcommand)]
    pub action: PolicyAction,
}

#[derive(Subcommand, Debug)]
pub enum PolicyAction {
    /// Evaluate policy rules against current ledger
    Check(PolicyCheckArgs),
}

#[derive(Args, Debug)]
pub struct PolicyCheckArgs {
    /// Only evaluate commits after this SHA (default: git merge-base with main)
    #[arg(long)]
    pub since: Option<String>,

    /// Output format: text | github-annotations
    #[arg(long, default_value = "text")]
    pub format: PolicyFormat,
}

#[derive(Debug, Clone, clap::ValueEnum)]
pub enum PolicyFormat {
    Text,
    GithubAnnotations,
}

// ── InstallCi ────────────────────────────────────────────────────────────────

#[derive(Args, Debug)]
pub struct InstallCiArgs {
    /// Overwrite existing workflow files without prompting
    #[arg(long)]
    pub force: bool,
}

#[derive(Args, Debug)]
pub struct InstallSkillArgs {
    /// Where to install the skill: project writes .cursor/skills, global writes ~/.agents/skills
    #[arg(long, default_value = "project")]
    pub scope: SkillScope,

    /// Overwrite an existing skill file
    #[arg(long)]
    pub force: bool,
}

#[derive(Debug, Clone, clap::ValueEnum, PartialEq, Eq)]
pub enum SkillScope {
    Project,
    Global,
}