Skip to main content

agent_trace/commands/
context.rs

1use crate::context::{
2    load_pending_updates, synthesize_context_content, synthesize_no_llm, write_context,
3};
4use crate::llm::Llm;
5use crate::observability::CliOutput;
6use crate::store::Store;
7use anyhow::Result;
8use chrono::Utc;
9use clap::Subcommand;
10use std::path::Path;
11
12#[derive(Subcommand, Debug)]
13pub enum ContextCmd {
14    /// Force a full re-synthesis of context.md.
15    Refresh,
16    /// Inject a specific update statement for the next synthesis.
17    Update {
18        /// The update statement to inject.
19        statement: String,
20    },
21    /// Print the current context.md to stdout.
22    Show,
23    /// List pending (unincorporated) context updates.
24    Updates,
25}
26
27pub fn run(store_root: &Path, cmd: ContextCmd, output: &dyn CliOutput) -> Result<()> {
28    match cmd {
29        ContextCmd::Show => {
30            let context_file = store_root.join("context.md");
31            if !context_file.exists() {
32                output
33                    .line("No context.md found. Run `agent-trace context refresh` to generate.")?;
34                return Ok(());
35            }
36            let content = std::fs::read_to_string(&context_file)?;
37            output.raw_stdout(&content)?;
38        }
39        ContextCmd::Update { statement } => {
40            let updates_file = store_root
41                .join(".agent-trace")
42                .join("context_updates.jsonl");
43            let entry = serde_json::json!({
44                "timestamp": Utc::now().to_rfc3339(),
45                "update": statement,
46                "incorporated": false,
47            });
48            let mut content = if updates_file.exists() {
49                std::fs::read_to_string(&updates_file)?
50            } else {
51                String::new()
52            };
53            content.push_str(&entry.to_string());
54            content.push('\n');
55            std::fs::write(&updates_file, content)?;
56            output.line(&format!("Context update queued: {statement}"))?;
57        }
58        ContextCmd::Updates => {
59            let pending = load_pending_updates(store_root)?;
60            if pending.is_empty() {
61                output.line("No pending context updates.")?;
62            } else {
63                output.line(&format!("{} pending update(s):", pending.len()))?;
64                for u in &pending {
65                    output.line(&format!("  [{}] {}", u.timestamp, u.update))?;
66                }
67            }
68        }
69        ContextCmd::Refresh => {
70            let store = Store::open(store_root)?;
71            let content = if let Ok(api) = Llm::from_store_root(store_root) {
72                synthesize_context_content(store_root, &store.manifest, &api, &[])?.0
73            } else {
74                synthesize_no_llm(store_root, &store.manifest)?
75            };
76            write_context(store_root, &content)?;
77            let plans = store.manifest.list(Some(&crate::types::DocType::Plan));
78            let refs = store.manifest.list(Some(&crate::types::DocType::Reference));
79            output.line(&format!(
80                "context.md refreshed ({} plans, {} reference docs).",
81                plans.len(),
82                refs.len()
83            ))?;
84        }
85    }
86    Ok(())
87}