agentstatedeveloper_cli/commands/
context_for.rs1use anyhow::Result;
12use clap::Args;
13use serde_json::{Value, json};
14
15use agentstatedeveloper_core::{AsgEffectStore, AsgIndexStore, AsgLedgerStore, Engine, IndexStore};
16
17use crate::commands::graph::AsdTimer;
18use crate::config::Config;
19
20pub(crate) use agentstatedeveloper_core::assemble_symbol_context;
24
25#[derive(Debug, Args)]
26pub struct ContextForArgs {
27 pub qnames: String,
30
31 #[arg(long)]
34 pub budget_tokens: Option<usize>,
35
36 #[arg(long, default_value = "false")]
38 pub include_body: bool,
39
40 #[arg(long)]
42 pub quiet: bool,
43
44 #[arg(long)]
46 pub timing: bool,
47}
48
49pub fn run(cfg: &Config, args: ContextForArgs) -> Result<()> {
50 let mut t = AsdTimer::new(args.timing);
51 if !args.quiet {
52 if let Some(warn) = agentstatedeveloper_core::stale_warning(&cfg.db_path, 3600) {
53 eprintln!("{warn}");
54 }
55 }
56 let engine = Engine::open_sqlite(&cfg.db_path)?;
57 t.phase("open_engine");
58 let index_store = AsgIndexStore::from_engine(&engine);
59 let effect_store = AsgEffectStore::from_engine(&engine);
60 let ledger_store = AsgLedgerStore::from_engine(&engine);
61
62 let id_map = index_store.build_id_map(&engine);
63 t.phase("build_id_map");
64
65 let qnames: Vec<&str> = args.qnames.split(',').map(|s| s.trim()).collect();
66 let budget_chars = args.budget_tokens.map(|t| t * 4);
67
68 let mut symbols_out = Vec::new();
69
70 for qname in &qnames {
71 let symbol = match index_store.get_symbol_by_qname(&engine.ref_name, qname)? {
72 Some(s) => s,
73 None => {
74 symbols_out.push(json!({ "qname": qname, "error": "symbol not found" }));
75 continue;
76 }
77 };
78 t.phase("symbol_lookup");
79
80 let sym_ctx = assemble_symbol_context(
81 &engine,
82 &index_store,
83 &effect_store,
84 &ledger_store,
85 &symbol,
86 &id_map,
87 args.include_body,
88 engine.fts.as_ref(),
89 None, )?;
91 t.phase("assemble_context");
92 symbols_out.push(sym_ctx);
93 }
94
95 let out = json!({
96 "query": args.qnames,
97 "count": symbols_out.len(),
98 "symbols": symbols_out,
99 });
100
101 let mut output = serde_json::to_string(&out)?;
104
105 if let Some(max_chars) = budget_chars {
107 if output.len() > max_chars {
108 let trimmed = trim_to_budget(&out, max_chars);
110 output = serde_json::to_string(&trimmed)?;
111 }
112 }
113
114 println!("{output}");
115 t.total("context-for");
116 Ok(())
117}
118
119fn trim_to_budget(out: &Value, _max_chars: usize) -> Value {
122 let mut v = out.clone();
126 if let Some(obj) = v.as_object_mut() {
127 obj.insert(
128 "_budget_warning".to_string(),
129 json!(format!(
130 "output exceeds budget; use --budget-tokens to trim, or filter to fewer qnames"
131 )),
132 );
133 }
134 v
135}