agentstatedeveloper_core/
context.rs1use std::collections::HashMap;
15
16use serde_json::{Value, json};
17
18use crate::effects::{AsgEffectStore, EffectStore};
19use crate::engine::Engine;
20use crate::index::{AsgIndexStore, IndexStore};
21use crate::ledger::{AsgLedgerStore, LedgerStore};
22use crate::schema::{LedgerKind, Symbol, VerificationStatus};
23use crate::search_fts::SearchFtsDb;
24use crate::{OwnershipSignal, discover_symbol_ownership, find_covering_tests};
25
26pub fn assemble_symbol_context(
42 engine: &Engine,
43 index_store: &AsgIndexStore<'_>,
44 effect_store: &AsgEffectStore<'_>,
45 ledger_store: &AsgLedgerStore<'_>,
46 symbol: &Symbol,
47 id_map: &HashMap<String, Symbol>,
48 include_body: bool,
49 fts: Option<&SearchFtsDb>,
50 ownership_hint: Option<&OwnershipSignal>,
51) -> crate::error::Result<Value> {
52 let callee_ids = index_store.get_callees(&engine.ref_name, &symbol.symbol_id)?;
54 let caller_ids = index_store.get_callers(&engine.ref_name, &symbol.symbol_id)?;
55
56 let resolve = |ids: &[String]| -> Vec<Value> {
57 ids.iter()
58 .map(|id| {
59 if let Some(s) = id_map.get(id) {
60 json!({ "qname": s.qname, "file": s.file, "line": s.start.line })
61 } else {
62 json!({ "symbol_id": id })
63 }
64 })
65 .collect()
66 };
67
68 let edge_ev: HashMap<String, (f64, bool)> = {
71 let tree = engine
72 .repo
73 .get_tree(
74 &engine.ref_name,
75 &crate::paths::edge_evidence_from_prefix(&symbol.symbol_id),
76 )
77 .unwrap_or(Value::Null);
78 let mut m = HashMap::new();
79 if let Some(obj) = tree.as_object() {
80 for (callee_id, v) in obj {
81 if let Ok(ev) =
82 serde_json::from_value::<crate::edge_confidence::EdgeEvidence>(v.clone())
83 {
84 m.insert(callee_id.clone(), (ev.confidence(), ev.static_known));
85 }
86 }
87 }
88 m
89 };
90 let round2 = |x: f64| (x * 100.0).round() / 100.0;
91 let callees_out: Vec<Value> = callee_ids
93 .iter()
94 .map(|id| {
95 let mut row = if let Some(s) = id_map.get(id) {
96 json!({ "qname": s.qname, "file": s.file, "line": s.start.line })
97 } else {
98 json!({ "symbol_id": id })
99 };
100 if let Some((conf, _)) = edge_ev.get(id) {
101 row["edge_confidence"] = json!(round2(*conf));
102 }
103 row
104 })
105 .collect();
106 let runtime_missed_callees: Vec<Value> = edge_ev
109 .iter()
110 .filter(|(_, (_, static_known))| !static_known)
111 .map(|(id, (conf, _))| {
112 let qname = id_map
113 .get(id)
114 .map(|s| s.qname.clone())
115 .unwrap_or_else(|| id.clone());
116 json!({ "qname": qname, "edge_confidence": round2(*conf) })
117 })
118 .collect();
119
120 let effects = effect_store.get_effects(&engine.ref_name, &symbol.symbol_id)?;
122
123 let ledger = ledger_store.list_entries(&engine.ref_name, &symbol.symbol_id)?;
125
126 let mut invariants: Vec<Value> = Vec::new();
128 let mut hazards: Vec<Value> = Vec::new();
129 let mut ownership: Vec<Value> = Vec::new();
130 let mut proofs: Vec<Value> = Vec::new();
131 let mut validation_scenarios: Vec<Value> = Vec::new();
132 let mut known_bugs: Vec<Value> = Vec::new();
133 let mut concepts: Vec<Value> = Vec::new();
134 let mut other_ledger: Vec<Value> = Vec::new();
135
136 for entry in &ledger {
137 let v = serde_json::to_value(entry)?;
138 match entry.kind {
139 LedgerKind::Invariant => invariants.push(v),
140 LedgerKind::Hazard => hazards.push(v),
141 LedgerKind::Ownership => ownership.push(v),
142 LedgerKind::Proof => proofs.push(v),
143 LedgerKind::ValidationScenario => validation_scenarios.push(v),
144 LedgerKind::KnownBug => known_bugs.push(v),
145 LedgerKind::Concept => concepts.push(v),
146 _ => other_ledger.push(v),
147 }
148 }
149
150 let mut sym_val = serde_json::to_value(symbol)?;
151 if !include_body {
152 if let Some(obj) = sym_val.as_object_mut() {
153 obj.remove("body");
154 }
155 }
156
157 let ownership_signal_owned;
161 let ownership_signal = if let Some(hint) = ownership_hint {
162 hint
163 } else {
164 ownership_signal_owned = discover_symbol_ownership(
165 &symbol.file,
166 symbol.start.line,
167 symbol.end.line,
168 symbol.doc.as_deref(),
169 );
170 &ownership_signal_owned
171 };
172 let mut discovered_ownership: serde_json::Map<String, Value> = serde_json::Map::new();
173 if let Some(ref author) = ownership_signal.primary_author {
174 discovered_ownership.insert("primary_author".into(), json!(author));
175 }
176 if let Some(ref doc_owner) = ownership_signal.doc_owner {
177 discovered_ownership.insert("doc_owner".into(), json!(doc_owner));
178 }
179 if !ownership_signal.recent_committers.is_empty() {
180 discovered_ownership.insert(
181 "recent_committers".into(),
182 json!(ownership_signal.recent_committers),
183 );
184 }
185 if !ownership_signal.annotated.is_empty() {
186 let annotated_val: Vec<Value> = ownership_signal
187 .annotated
188 .iter()
189 .map(|a| {
190 json!({
191 "name": a.name,
192 "source": serde_json::to_value(a.source).unwrap_or(json!("unknown")),
193 })
194 })
195 .collect();
196 discovered_ownership.insert("annotated".into(), json!(annotated_val));
197 }
198
199 let covering_tests: Vec<Value> = find_covering_tests(fts, &symbol.qname)
201 .into_iter()
202 .map(|ct| {
203 json!({
204 "qname": ct.qname,
205 "file": ct.file,
206 "line": ct.line,
207 "run_command": ct.run_command,
208 })
209 })
210 .collect();
211
212 let effects_detail: Vec<Value> = if let Some(ref decl) = effects {
216 let mismatch_effects: std::collections::HashSet<String> = decl
217 .verification
218 .as_ref()
219 .map(|v| {
220 v.mismatches
221 .iter()
222 .map(|m| m.effect.as_str().to_string())
223 .collect()
224 })
225 .unwrap_or_default();
226 let overall_ok = decl
227 .verification
228 .as_ref()
229 .map(|v| matches!(v.status, VerificationStatus::Ok))
230 .unwrap_or(false);
231 decl.declared
232 .iter()
233 .map(|e| {
234 let effect_str = e.effect.as_str();
235 let is_mismatched = mismatch_effects.contains(effect_str);
236 let status = if decl.verification.is_none() {
237 "unverified"
238 } else if is_mismatched {
239 "mismatch"
240 } else if overall_ok {
241 "ok"
242 } else {
243 "ok"
244 };
245 let mut obj = serde_json::Map::new();
246 obj.insert("effect".into(), json!(effect_str));
247 obj.insert("status".into(), json!(status));
248 if let Some(ref adapter) = e.adapter {
249 obj.insert("adapter".into(), json!(adapter));
250 }
251 if let Some(ref pattern) = e.source_pattern {
252 obj.insert("source_pattern".into(), json!(pattern));
253 }
254 if let Some(note) = &e.note {
255 obj.insert("note".into(), json!(note));
256 }
257 Value::Object(obj)
258 })
259 .collect()
260 } else {
261 Vec::new()
262 };
263
264 let cross_service = {
269 let tree = engine
270 .repo
271 .get_tree(&engine.ref_name, "/asd/v1/index/endpoints")
272 .unwrap_or(Value::Null);
273 let all = crate::cross_service::endpoints_from_tree(&tree);
274 let mine: Vec<&crate::cross_service::ServiceEndpoint> = all
275 .iter()
276 .filter(|e| e.symbol_id == symbol.symbol_id)
277 .collect();
278 if mine.is_empty() {
279 None
280 } else {
281 let edges = crate::cross_service::match_edges(&all);
282 let touching: Vec<Value> = edges
283 .iter()
284 .filter(|e| {
285 e.from.symbol_id == symbol.symbol_id || e.to.symbol_id == symbol.symbol_id
286 })
287 .map(|e| {
288 let is_handler = e.to.symbol_id == symbol.symbol_id;
289 let peer = if is_handler { &e.from } else { &e.to };
290 json!({
291 "role": if is_handler { "handler" } else { "consumer" },
292 "contract": e.contract,
293 "peer_qname": peer.qname,
294 "peer_repo": peer.repo_id,
295 "cross_repo": e.cross_repo,
296 "confidence": e.confidence,
297 })
298 })
299 .collect();
300 Some(json!({
301 "endpoints": mine.iter().map(|e| json!({
302 "transport": e.transport,
303 "direction": e.direction,
304 "contract": e.contract,
305 "confidence": e.confidence,
306 })).collect::<Vec<_>>(),
307 "edges": touching,
308 }))
309 }
310 };
311
312 let mut out = json!({
315 "symbol": sym_val,
316 "invariants": invariants,
317 "hazards": hazards,
318 "known_bugs": known_bugs,
319 "concepts": concepts,
320 "ownership": ownership,
321 "ownership_discovery": discovered_ownership,
322 "covering_tests": covering_tests,
323 "validation_scenarios": validation_scenarios,
324 "callers": resolve(&caller_ids),
325 "callees": callees_out,
326 "effects": effects,
327 "effects_detail": effects_detail,
328 "proofs": proofs,
329 "decisions_and_notes": other_ledger,
330 });
331 if let Some(cs) = cross_service {
332 out["cross_service"] = cs;
333 }
334
335 let dataflow = {
338 let tree = engine
339 .repo
340 .get_tree(&engine.ref_name, "/asd/v1/index/dataflow")
341 .unwrap_or(Value::Null);
342 let all = crate::dataflow::edges_from_tree(&tree);
343 let incoming: Vec<Value> = all
344 .iter()
345 .filter(|e| e.to_symbol_id == symbol.symbol_id)
346 .map(|e| json!({ "param": e.param, "from_arg": e.arg, "from_qname": e.from_qname }))
347 .collect();
348 let outgoing: Vec<Value> = all
349 .iter()
350 .filter(|e| e.from_symbol_id == symbol.symbol_id)
351 .map(|e| json!({ "arg": e.arg, "to_param": e.param, "to_qname": e.to_qname }))
352 .collect();
353 if incoming.is_empty() && outgoing.is_empty() {
354 None
355 } else {
356 Some(json!({ "incoming": incoming, "outgoing": outgoing }))
357 }
358 };
359 if let Some(df) = dataflow {
360 out["dataflow"] = df;
361 }
362 if !runtime_missed_callees.is_empty() {
364 out["runtime_missed_callees"] = json!(runtime_missed_callees);
365 }
366 Ok(out)
367}