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 Ok(ir)
30}
31
32pub fn jsonl(ir: &scc_core::SystemIr) -> crate::Result<Vec<String>> {
34 let mut out = Vec::new();
35 out.push(serde_json::to_string(&serde_json::json!({
36 "type": "repository", "repository": ir.repository
37 }))?);
38 out.push(serde_json::to_string(&serde_json::json!({
39 "type": "snapshot", "snapshot": ir.snapshot, "schema_version": ir.schema_version
40 }))?);
41 for e in &ir.entities {
42 out.push(serde_json::to_string(&serde_json::json!({"type": "entity", "entity": e}))?);
43 }
44 for r in &ir.relationships {
45 out.push(serde_json::to_string(&serde_json::json!({"type": "relationship", "relationship": r}))?);
46 }
47 for f in &ir.flows {
48 out.push(serde_json::to_string(&serde_json::json!({"type": "flow", "flow": f}))?);
49 }
50 for i in &ir.invariants {
51 out.push(serde_json::to_string(&serde_json::json!({"type": "invariant", "invariant": i}))?);
52 }
53 for e in &ir.evidence {
54 out.push(serde_json::to_string(&serde_json::json!({"type": "evidence", "evidence": e}))?);
55 }
56 Ok(out)
57}
58
59pub fn ccg(ir: &scc_core::SystemIr) -> crate::Result<serde_json::Value> {
61 let l1: Vec<serde_json::Value> = ir
62 .entities
63 .iter()
64 .filter(|e| {
65 e.kind == kinds::COMPONENT
66 || e.kind == kinds::SERVICE
67 || e.kind == kinds::DATA_STORE
68 || e.kind == kinds::DEPLOYMENT_UNIT
69 || e.kind == kinds::EXTERNAL_API
70 })
71 .map(|e| {
72 serde_json::json!({
73 "id": e.id,
74 "name": e.name,
75 "kind": e.kind,
76 "attributes": e.attributes,
77 })
78 })
79 .collect();
80 let l2: Vec<serde_json::Value> = ir
81 .entities
82 .iter()
83 .filter(|e| e.kind == kinds::SYMBOL)
84 .map(|e| {
85 serde_json::json!({
86 "id": e.id,
87 "name": e.name,
88 "kind": e.attributes.get("kind").cloned().unwrap_or(serde_json::json!("symbol")),
89 "file": e.attributes.get("file").cloned().unwrap_or_default(),
90 })
91 })
92 .collect();
93 Ok(serde_json::json!({
94 "schema": "ccg",
95 "producer": "scc",
96 "repository": ir.repository,
97 "snapshot": ir.snapshot,
98 "layers": {
99 "L0": {
100 "manifest": {
101 "repository": ir.repository.name,
102 "revision": ir.snapshot.revision,
103 "entity_count": ir.entities.len(),
104 "relationship_count": ir.relationships.len(),
105 }
106 },
107 "L1": { "architecture": l1 },
108 "L2": { "symbols": l2 },
109 }
110 }))
111}
112
113pub fn capsule(root: &std::path::Path) -> crate::Result<String> {
115 let store = crate::workspace::open_store(root)?;
116 let config = crate::workspace::load_config(root)?;
117 let stale = crate::workspace::stale_paths(&store)?;
118 let engine = crate::workspace::open_engine(&store, &config, stale)?;
119 let overview = engine.context().overview()?;
120 let revision = overview.repository_revision.clone();
121 let repo = store.repository();
122 let paths: Vec<String> = store
123 .all_files()
124 .unwrap_or_default()
125 .into_iter()
126 .map(|(p, _, _, _, _)| p)
127 .collect();
128 let skeleton = scc_context::skeleton::build_skeleton(
129 &paths,
130 scc_context::skeleton::skeleton_budget(config.context.startup_tokens),
131 );
132 Ok(format!(
133 "<!-- 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",
134 repo.id,
135 revision,
136 scc_core::now_rfc3339(),
137 overview.content,
138 skeleton.text
139 ))
140}
141
142pub fn model_get(store: &scc_store::Store) -> crate::Result<serde_json::Value> {
151 let ir = system_ir(store)?;
152 let (revision, indexed_at, branch) = match store.snapshot_status()? {
153 Some((snap, _)) => (snap.revision, Some(snap.indexed_at), snap.branch),
154 None => ("not-indexed".to_string(), None, None),
155 };
156 Ok(serde_json::json!({
157 "repository": store.repository(),
158 "revision": revision,
159 "branch": branch,
160 "indexed_at": indexed_at,
161 "epoch": store.model_epoch()?,
162 "stats": store.stats()?,
163 "files": store.all_files()?,
164 "entities": ir.entities,
165 "relationships": ir.relationships,
166 "evidence": ir.evidence,
167 "components": store.components()?,
168 "flows": store.flows()?,
169 "flow_graphs": store.flow_graphs()?,
170 "invariants": ir.invariants,
171 }))
172}
173
174pub fn diagram(store: &scc_store::Store, format: &str) -> crate::Result<serde_json::Value> {
179 let model = crate::diagram::build_diagram_model(store)?;
180 let text = match format {
181 "mermaid" => crate::diagram::render_mermaid(&model),
182 "svg" => crate::diagram::render_svg(&model),
183 other => {
184 return Err(crate::EngineError::Other(format!(
185 "unknown diagram format '{other}' (use mermaid|svg)"
186 )))
187 }
188 };
189 Ok(serde_json::json!({
190 "format": format,
191 "text": text,
192 "nodes": model.nodes.len(),
193 "edges": model.edges.len(),
194 "flows": model.flows.len(),
195 }))
196}