chronicle/cli/mod.rs
1pub mod annotate;
2pub mod backfill;
3pub mod context;
4pub mod correct;
5pub mod deps;
6pub mod doctor;
7pub mod export;
8pub mod flag;
9pub mod history;
10pub mod import;
11pub mod init;
12pub mod read;
13pub mod reconfigure;
14pub mod setup;
15pub mod show;
16pub mod summary;
17pub mod sync;
18
19use clap::{Parser, Subcommand};
20
21#[derive(Parser)]
22#[command(
23 name = "git-chronicle",
24 version,
25 about = "AI-powered commit annotation"
26)]
27pub struct Cli {
28 #[command(subcommand)]
29 pub command: Commands,
30}
31
32#[derive(Subcommand)]
33pub enum Commands {
34 /// One-time machine-wide setup (provider, skills, hooks, CLAUDE.md)
35 Setup {
36 /// Overwrite existing files without prompting
37 #[arg(long)]
38 force: bool,
39
40 /// Print what would be done without writing
41 #[arg(long)]
42 dry_run: bool,
43
44 /// Skip installing Claude Code skills
45 #[arg(long)]
46 skip_skills: bool,
47
48 /// Skip installing Claude Code hooks
49 #[arg(long)]
50 skip_hooks: bool,
51
52 /// Skip modifying ~/.claude/CLAUDE.md
53 #[arg(long)]
54 skip_claude_md: bool,
55 },
56
57 /// Rerun the LLM provider selection prompt
58 Reconfigure,
59
60 /// Annotate historical commits that lack Chronicle annotations
61 Backfill {
62 /// Maximum number of commits to annotate
63 #[arg(long, default_value = "20")]
64 limit: usize,
65
66 /// List commits that would be annotated without calling the LLM
67 #[arg(long)]
68 dry_run: bool,
69 },
70
71 /// Initialize chronicle in the current repository
72 Init {
73 /// Disable notes sync (sync is enabled by default)
74 #[arg(long)]
75 no_sync: bool,
76
77 /// Skip hook installation
78 #[arg(long)]
79 no_hooks: bool,
80
81 /// LLM provider to use
82 #[arg(long)]
83 provider: Option<String>,
84
85 /// LLM model to use
86 #[arg(long)]
87 model: Option<String>,
88
89 /// Run backfill after init (annotate last 20 commits)
90 #[arg(long)]
91 backfill: bool,
92 },
93
94 /// Manage annotation context
95 Context {
96 #[command(subcommand)]
97 action: ContextAction,
98 },
99
100 /// Read annotations for a file
101 Read {
102 /// File path to read annotations for
103 path: String,
104
105 /// Filter by AST anchor name
106 #[arg(long)]
107 anchor: Option<String>,
108
109 /// Filter by line range (format: start:end)
110 #[arg(long)]
111 lines: Option<String>,
112 },
113
114 /// Annotate a specific commit
115 Annotate {
116 /// Commit to annotate (default: HEAD)
117 #[arg(long, default_value = "HEAD")]
118 commit: String,
119
120 /// Run synchronously
121 #[arg(long)]
122 sync: bool,
123
124 /// Read AnnotateInput JSON from stdin (live annotation path, zero LLM cost)
125 #[arg(long)]
126 live: bool,
127
128 /// Comma-separated source commit SHAs for squash synthesis (CI usage)
129 #[arg(long)]
130 squash_sources: Option<String>,
131
132 /// Old commit SHA to migrate annotation from (amend re-annotation)
133 #[arg(long)]
134 amend_source: Option<String>,
135 },
136
137 /// Flag a region annotation as potentially inaccurate
138 Flag {
139 /// File path relative to repository root
140 path: String,
141
142 /// Optional AST anchor name to scope the flag to a specific region
143 anchor: Option<String>,
144
145 /// Reason for flagging this annotation
146 #[arg(long)]
147 reason: String,
148 },
149
150 /// Apply a precise correction to a specific annotation field
151 Correct {
152 /// Commit SHA of the annotation to correct
153 sha: String,
154
155 /// AST anchor name of the region within the annotation
156 #[arg(long)]
157 region: String,
158
159 /// Annotation field to correct (intent, reasoning, constraints, risk_notes, semantic_dependencies, tags)
160 #[arg(long)]
161 field: String,
162
163 /// Specific value to remove or mark as incorrect
164 #[arg(long)]
165 remove: Option<String>,
166
167 /// Replacement or amendment text
168 #[arg(long)]
169 amend: Option<String>,
170 },
171
172 /// Manage notes sync with remotes
173 Sync {
174 #[command(subcommand)]
175 action: SyncAction,
176 },
177
178 /// Export annotations as JSONL
179 Export {
180 /// Write to file instead of stdout
181 #[arg(short, long)]
182 output: Option<String>,
183 },
184
185 /// Import annotations from a JSONL file
186 Import {
187 /// JSONL file to import
188 file: String,
189
190 /// Overwrite existing annotations
191 #[arg(long)]
192 force: bool,
193
194 /// Show what would be imported without writing
195 #[arg(long)]
196 dry_run: bool,
197 },
198
199 /// Run diagnostic checks on the chronicle setup
200 Doctor {
201 /// Output as JSON
202 #[arg(long)]
203 json: bool,
204 },
205
206 /// Find code that depends on a given file/anchor (dependency inversion)
207 Deps {
208 /// File path to query
209 path: String,
210
211 /// AST anchor name to query
212 anchor: Option<String>,
213
214 /// Output format (json or pretty)
215 #[arg(long, default_value = "json")]
216 format: String,
217
218 /// Maximum number of results to return
219 #[arg(long, default_value = "50")]
220 max_results: u32,
221
222 /// Maximum number of commits to scan
223 #[arg(long, default_value = "500")]
224 scan_limit: u32,
225 },
226
227 /// Show annotation timeline for a file/anchor across commits
228 History {
229 /// File path to query
230 path: String,
231
232 /// AST anchor name to query
233 anchor: Option<String>,
234
235 /// Maximum number of timeline entries
236 #[arg(long, default_value = "10")]
237 limit: u32,
238
239 /// Output format (json or pretty)
240 #[arg(long, default_value = "json")]
241 format: String,
242
243 /// Follow related annotation links
244 #[arg(long, default_value = "true")]
245 follow_related: bool,
246 },
247
248 /// Interactive TUI explorer for annotated source code
249 Show {
250 /// File path to show
251 path: String,
252
253 /// Focus on a specific AST anchor
254 anchor: Option<String>,
255
256 /// Commit to show file at
257 #[arg(long, default_value = "HEAD")]
258 commit: String,
259
260 /// Force non-interactive plain-text output
261 #[arg(long)]
262 no_tui: bool,
263 },
264
265 /// Show condensed annotation summary for a file
266 Summary {
267 /// File path to query
268 path: String,
269
270 /// Filter to a specific AST anchor
271 #[arg(long)]
272 anchor: Option<String>,
273
274 /// Output format (json or pretty)
275 #[arg(long, default_value = "json")]
276 format: String,
277 },
278}
279
280#[derive(Subcommand)]
281pub enum SyncAction {
282 /// Enable notes sync for a remote
283 Enable {
284 /// Remote name (default: origin)
285 #[arg(long, default_value = "origin")]
286 remote: String,
287 },
288
289 /// Show sync status
290 Status {
291 /// Remote name (default: origin)
292 #[arg(long, default_value = "origin")]
293 remote: String,
294 },
295
296 /// Fetch and merge remote notes
297 Pull {
298 /// Remote name (default: origin)
299 #[arg(long, default_value = "origin")]
300 remote: String,
301 },
302}
303
304#[derive(Subcommand)]
305pub enum ContextAction {
306 /// Set pending context for the next commit
307 Set {
308 #[arg(long)]
309 task: Option<String>,
310
311 #[arg(long)]
312 reasoning: Option<String>,
313
314 #[arg(long)]
315 dependencies: Option<String>,
316
317 #[arg(long)]
318 tags: Vec<String>,
319 },
320
321 /// Show current pending context
322 Show,
323
324 /// Clear pending context
325 Clear,
326}