1use std::path::PathBuf;
2
3use crate::error::chronicle_error::{GitSnafu, IoSnafu};
4use crate::error::Result;
5use crate::git::CliOps;
6use crate::git::GitOps;
7use crate::hooks::install_hooks;
8use crate::sync::enable_sync;
9use snafu::ResultExt;
10
11use super::util::find_git_dir;
12
13pub fn run(
14 no_sync: bool,
15 no_hooks: bool,
16 provider: Option<String>,
17 model: Option<String>,
18 backfill: bool,
19) -> Result<()> {
20 let git_dir = find_git_dir()?;
22
23 let chronicle_dir = git_dir.join("chronicle");
25 std::fs::create_dir_all(&chronicle_dir).context(IoSnafu)?;
26
27 let repo_dir = git_dir.parent().unwrap_or(&git_dir).to_path_buf();
29 let ops = CliOps::new(repo_dir.clone());
30
31 ops.config_set("chronicle.enabled", "true")
32 .context(GitSnafu)?;
33
34 if let Some(ref p) = provider {
35 ops.config_set("chronicle.provider", p).context(GitSnafu)?;
36 }
37
38 if let Some(ref m) = model {
39 ops.config_set("chronicle.model", m).context(GitSnafu)?;
40 }
41
42 if !no_hooks {
44 install_hooks(&git_dir)?;
45 eprintln!("installed post-commit hook");
46 }
47
48 if !no_sync {
50 ops.config_set("chronicle.sync", "true").context(GitSnafu)?;
51 let remote = "origin";
52 match enable_sync(&repo_dir, remote) {
53 Ok(()) => eprintln!("notes sync enabled for remote '{remote}'"),
54 Err(e) => eprintln!("warning: could not enable notes sync: {e}"),
55 }
56 }
57
58 eprintln!("chronicle initialized in {}", chronicle_dir.display());
59
60 let unannotated = count_unannotated(&ops);
64 if unannotated > 0 {
65 eprintln!();
66 eprintln!(
67 "Found {} unannotated commits (of last 100). Run `git chronicle backfill --limit 20` to annotate recent history.",
68 unannotated
69 );
70 }
71
72 if let Ok(home) = std::env::var("HOME") {
74 let skills_dir = PathBuf::from(&home)
75 .join(".claude")
76 .join("skills")
77 .join("chronicle");
78 if !skills_dir.exists() {
79 eprintln!();
80 eprintln!("TIP: Run `git chronicle setup` to install Claude Code skills globally.");
81 }
82 }
83
84 if backfill {
86 eprintln!();
87 eprintln!("Running backfill (limit 20)...");
88 if let Err(e) = crate::cli::backfill::run(20, false) {
89 eprintln!("warning: backfill failed: {e}");
90 }
91 }
92
93 Ok(())
94}
95
96fn count_unannotated(ops: &dyn GitOps) -> usize {
98 let output = match std::process::Command::new("git")
99 .args(["log", "--format=%H", "-100"])
100 .output()
101 {
102 Ok(o) if o.status.success() => o,
103 _ => return 0,
104 };
105
106 let shas: Vec<String> = String::from_utf8_lossy(&output.stdout)
107 .lines()
108 .map(|s| s.to_string())
109 .filter(|s| !s.is_empty())
110 .collect();
111
112 let mut unannotated = 0;
113 for sha in &shas {
114 if let Ok(false) = ops.note_exists(sha) {
115 unannotated += 1;
116 }
117 }
118 unannotated
119}