1use scc_core::kinds;
7
8pub fn system_ir(store: &scc_store::Store) -> crate::Result<scc_core::SystemIr> {
10 let repository = store.repository();
11 let snapshot = store
12 .latest_snapshot()?
13 .unwrap_or(scc_core::Snapshot {
14 revision: "not-indexed".into(),
15 branch: None,
16 indexed_at: scc_core::now_rfc3339(),
17 });
18 let mut ir = scc_core::SystemIr::empty(repository, snapshot);
19 let mut seen = std::collections::HashSet::new();
20 for e in store.all_entities()? {
21 if seen.insert(e.id.clone()) {
22 ir.entities.push(e);
23 }
24 }
25 ir.relationships = store.all_relationships()?;
26 ir.flows = store.flows()?;
27 ir.invariants = store.invariants()?;
28 ir.evidence = store.all_evidence()?;
29 let mut ext: std::collections::BTreeMap<String, Vec<String>> = std::collections::BTreeMap::new();
33 let mut note = |provider: &str, id: &str| {
34 ext.entry(provider.to_string()).or_default().push(id.to_string());
35 };
36 for en in &ir.entities {
37 if let Some(rest) = en.kind.strip_prefix("plugin:") {
38 let p = rest.split('/').next().unwrap_or(rest);
39 note(p, &en.id);
40 }
41 for ev in &en.evidence {
42 if let Some(rest) = ev.strip_prefix("plugin:") {
43 let p = rest.split('/').next().unwrap_or(rest);
44 note(p, &en.id);
45 }
46 }
47 }
48 for r in &ir.relationships {
49 if let Some(rest) = r.predicate.strip_prefix("plugin:") {
50 let p = rest.split('/').next().unwrap_or(rest);
51 note(p, &r.id);
52 }
53 for ev in &r.evidence {
54 if let Some(rest) = ev.strip_prefix("plugin:") {
55 let p = rest.split('/').next().unwrap_or(rest);
56 note(p, &r.id);
57 }
58 }
59 }
60 for v in ext.values_mut() {
61 v.sort();
62 v.dedup();
63 }
64 ir.extensions = ext;
65 Ok(ir)
66}
67
68pub fn jsonl(ir: &scc_core::SystemIr) -> crate::Result<Vec<String>> {
70 let mut out = Vec::new();
71 out.push(serde_json::to_string(&serde_json::json!({
72 "type": "repository", "repository": ir.repository
73 }))?);
74 out.push(serde_json::to_string(&serde_json::json!({
75 "type": "snapshot", "snapshot": ir.snapshot, "schema_version": ir.schema_version
76 }))?);
77 for e in &ir.entities {
78 out.push(serde_json::to_string(&serde_json::json!({"type": "entity", "entity": e}))?);
79 }
80 for r in &ir.relationships {
81 out.push(serde_json::to_string(&serde_json::json!({"type": "relationship", "relationship": r}))?);
82 }
83 for f in &ir.flows {
84 out.push(serde_json::to_string(&serde_json::json!({"type": "flow", "flow": f}))?);
85 }
86 for i in &ir.invariants {
87 out.push(serde_json::to_string(&serde_json::json!({"type": "invariant", "invariant": i}))?);
88 }
89 for e in &ir.evidence {
90 out.push(serde_json::to_string(&serde_json::json!({"type": "evidence", "evidence": e}))?);
91 }
92 Ok(out)
93}
94
95pub fn ccg(ir: &scc_core::SystemIr) -> crate::Result<serde_json::Value> {
97 let l1: Vec<serde_json::Value> = ir
98 .entities
99 .iter()
100 .filter(|e| {
101 e.kind == kinds::COMPONENT
102 || e.kind == kinds::SERVICE
103 || e.kind == kinds::DATA_STORE
104 || e.kind == kinds::DEPLOYMENT_UNIT
105 || e.kind == kinds::EXTERNAL_API
106 })
107 .map(|e| {
108 serde_json::json!({
109 "id": e.id,
110 "name": e.name,
111 "kind": e.kind,
112 "attributes": e.attributes,
113 })
114 })
115 .collect();
116 let l2: Vec<serde_json::Value> = ir
117 .entities
118 .iter()
119 .filter(|e| e.kind == kinds::SYMBOL)
120 .map(|e| {
121 serde_json::json!({
122 "id": e.id,
123 "name": e.name,
124 "kind": e.attributes.get("kind").cloned().unwrap_or(serde_json::json!("symbol")),
125 "file": e.attributes.get("file").cloned().unwrap_or_default(),
126 })
127 })
128 .collect();
129 Ok(serde_json::json!({
130 "schema": "ccg",
131 "producer": "scc",
132 "repository": ir.repository,
133 "snapshot": ir.snapshot,
134 "layers": {
135 "L0": {
136 "manifest": {
137 "repository": ir.repository.name,
138 "revision": ir.snapshot.revision,
139 "entity_count": ir.entities.len(),
140 "relationship_count": ir.relationships.len(),
141 }
142 },
143 "L1": { "architecture": l1 },
144 "L2": { "symbols": l2 },
145 }
146 }))
147}
148
149pub fn capsule(root: &std::path::Path) -> crate::Result<String> {
151 let store = crate::workspace::open_store(root)?;
152 let config = crate::workspace::load_config(root)?;
153 let stale = crate::workspace::stale_paths(&store)?;
154 let engine = crate::workspace::open_engine(&store, &config, stale)?;
155 let overview = engine.context().overview()?;
156 let revision = overview.repository_revision.clone();
157 let repo = store.repository();
158 let paths: Vec<String> = store
159 .all_files()
160 .unwrap_or_default()
161 .into_iter()
162 .map(|(p, _, _, _, _)| p)
163 .collect();
164 let skeleton = scc_context::skeleton::build_skeleton(
165 &paths,
166 scc_context::skeleton::skeleton_budget(config.context.startup_tokens),
167 );
168 Ok(format!(
169 "<!-- SCC-CAPSULE v1 repo={} revision={} generated={} -->\n# SYSTEM CAPSULE\n\n{}\n## REPOSITORY SKELETON\n\n{}\n\n(Physical layout above; run `scc context startup` for the fused live architecture.)\n",
170 repo.id,
171 revision,
172 scc_core::now_rfc3339(),
173 overview.content,
174 skeleton.text
175 ))
176}
177
178pub fn model_get(store: &scc_store::Store) -> crate::Result<serde_json::Value> {
187 let ir = system_ir(store)?;
188 let (revision, indexed_at, branch) = match store.snapshot_status()? {
189 Some((snap, _)) => (snap.revision, Some(snap.indexed_at), snap.branch),
190 None => ("not-indexed".to_string(), None, None),
191 };
192 Ok(serde_json::json!({
193 "repository": store.repository(),
194 "revision": revision,
195 "branch": branch,
196 "indexed_at": indexed_at,
197 "epoch": store.model_epoch()?,
198 "stats": store.stats()?,
199 "files": store.all_files()?,
200 "entities": ir.entities,
201 "relationships": ir.relationships,
202 "evidence": ir.evidence,
203 "components": store.components()?,
204 "flows": store.flows()?,
205 "flow_graphs": store.flow_graphs()?,
206 "invariants": ir.invariants,
207 }))
208}
209
210pub fn diagram(store: &scc_store::Store, format: &str) -> crate::Result<serde_json::Value> {
215 let model = crate::diagram::build_diagram_model(store)?;
216 let text = match format {
217 "mermaid" => crate::diagram::render_mermaid(&model),
218 "svg" => crate::diagram::render_svg(&model),
219 other => {
220 return Err(crate::EngineError::Other(format!(
221 "unknown diagram format '{other}' (use mermaid|svg)"
222 )))
223 }
224 };
225 Ok(serde_json::json!({
226 "format": format,
227 "text": text,
228 "nodes": model.nodes.len(),
229 "edges": model.edges.len(),
230 "flows": model.flows.len(),
231 }))
232}