Skip to main content

chronicle/annotate/
mod.rs

1pub mod filter;
2pub mod gather;
3pub mod squash;
4
5use crate::error::{chronicle_error, Result};
6use crate::git::GitOps;
7use crate::provider::LlmProvider;
8use crate::schema::{Annotation, ContextLevel};
9use snafu::ResultExt;
10
11/// The main annotation entry point. Gathers context, checks filters,
12/// runs the agent, and writes the annotation as a git note.
13pub fn run(
14    git_ops: &dyn GitOps,
15    provider: &dyn LlmProvider,
16    commit: &str,
17    _sync: bool,
18) -> Result<Annotation> {
19    // 1. Gather context
20    let context = gather::build_context(git_ops, commit)?;
21
22    // 2. Pre-LLM filter
23    let decision = filter::pre_llm_filter(&context);
24
25    let annotation = match decision {
26        filter::FilterDecision::Skip(reason) => {
27            tracing::info!("Skipping annotation: {}", reason);
28            // Return a minimal annotation with just the summary
29            Annotation::new_initial(
30                context.commit_sha.clone(),
31                format!("Skipped: {}", reason),
32                ContextLevel::Inferred,
33            )
34        }
35        filter::FilterDecision::Trivial(reason) => {
36            tracing::info!("Trivial commit: {}", reason);
37            // Build a minimal annotation without calling LLM
38            let mut ann = Annotation::new_initial(
39                context.commit_sha.clone(),
40                context.commit_message.clone(),
41                ContextLevel::Inferred,
42            );
43            // Carry over task from author context if present
44            if let Some(ref author_ctx) = context.author_context {
45                ann.task = author_ctx.task.clone();
46            }
47            ann
48        }
49        filter::FilterDecision::Annotate => {
50            // Call the agent loop for full LLM annotation
51            let (regions, cross_cutting, summary) =
52                crate::agent::run_agent_loop(provider, git_ops, &context)
53                    .context(chronicle_error::AgentSnafu)?;
54
55            let context_level = if context.author_context.is_some() {
56                ContextLevel::Enhanced
57            } else {
58                ContextLevel::Inferred
59            };
60
61            let mut ann =
62                Annotation::new_initial(context.commit_sha.clone(), summary, context_level);
63            ann.regions = regions;
64            ann.cross_cutting = cross_cutting;
65            // Carry over task from author context if present
66            if let Some(ref author_ctx) = context.author_context {
67                ann.task = author_ctx.task.clone();
68            }
69            ann
70        }
71    };
72
73    // 3. Serialize and write as git note
74    let json = serde_json::to_string_pretty(&annotation).context(chronicle_error::JsonSnafu)?;
75    git_ops
76        .note_write(commit, &json)
77        .context(chronicle_error::GitSnafu)?;
78
79    Ok(annotation)
80}