Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
8a43767
docs: design call graph resolution pipeline hardening
forhappy Jul 28, 2026
9bb83bd
docs: design Compass Code Graph v1
forhappy Jul 28, 2026
ee24b41
docs: design VS Code CLI onboarding
forhappy Jul 28, 2026
2780b50
docs: plan Compass Code Graph v1 implementation
forhappy Jul 28, 2026
5407c6d
docs: plan VS Code CLI onboarding
forhappy Jul 28, 2026
7c33387
feat: add official Windows Compass installer
forhappy Jul 28, 2026
dcefddd
feat(vscode): activate verified Compass CLIs live
forhappy Jul 28, 2026
c7fec8d
feat(viewer): add Compass CLI onboarding states
forhappy Jul 28, 2026
ebf7190
feat(vscode): install Compass in the integrated terminal
forhappy Jul 28, 2026
03445f8
feat(vscode): guide missing CLI users through setup
forhappy Jul 28, 2026
e7350de
fix(vscode): harden onboarding recovery and packaging
forhappy Jul 28, 2026
35cb913
docs: make Code Graph plan implementation-first
forhappy Jul 28, 2026
04ebf77
docs: finalize call graph hardening plan
forhappy Jul 28, 2026
da9c336
docs: design scalable architecture flow
forhappy Jul 29, 2026
2c2c24a
docs: plan scalable architecture flow
forhappy Jul 29, 2026
93708fd
fix(vscode): support bounded large architecture exports
forhappy Jul 29, 2026
ab63d21
feat(callflow): preserve complete architecture evidence
forhappy Jul 29, 2026
4890b6c
feat(vscode): index architecture data in the extension host
forhappy Jul 29, 2026
204b02e
feat(vscode): visualize complete architecture flow
forhappy Jul 29, 2026
9bf3e4e
docs(vscode): document scalable architecture flow
forhappy Jul 29, 2026
3cc527d
chore(vscode): release 0.1.9
forhappy Jul 29, 2026
c190a5f
feat(vscode): clarify architecture map layout
forhappy Jul 29, 2026
785e2e1
fix(vscode): retain callflow schema v1 compatibility
forhappy Jul 29, 2026
8cea557
refactor(vscode): hard-cutover callflow to schema v1
forhappy Jul 30, 2026
6887bc2
Merge remote-tracking branch 'origin/main' into codex/harden-call-gra…
forhappy Jul 30, 2026
337c292
feat(vscode): improve architecture flow layout
forhappy Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/compass-cli/tests/viewer_export_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ fn callflow_json_exposes_the_shared_architecture_model() -> Result<(), Box<dyn E
let value: Value = serde_json::from_slice(&output.stdout)?;
assert_eq!(value["schema"], "compass.viewer.callflow/1");
assert_eq!(value["statistics"]["inferred"], 1);
assert_eq!(value["coverage"]["crossSection"], 1);
assert_eq!(value["crossSectionCalls"][0]["source"], "run");
assert_eq!(value["crossSectionCalls"][0]["target"], "store");
assert_eq!(value["crossSectionCalls"][0]["confidence"], "inferred");
assert!(
value["sections"]
.as_array()
Expand Down
111 changes: 107 additions & 4 deletions crates/compass-output/src/callflow_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub struct CallflowViewModel {
pub title: String,
pub sections: Vec<CallflowViewSection>,
pub overview_links: Vec<CallflowViewLink>,
pub cross_section_calls: Vec<CallflowCrossSectionCall>,
pub coverage: CallflowCoverage,
pub report_highlights: Vec<String>,
pub statistics: CallflowStatistics,
pub provenance: CallflowProvenance,
Expand All @@ -26,6 +28,8 @@ pub struct CallflowViewSection {
pub id: String,
pub name: String,
pub communities: Vec<String>,
pub node_count: usize,
pub internal_call_count: usize,
pub nodes: Vec<CallflowViewNode>,
pub edges: Vec<CallflowViewEdge>,
}
Expand All @@ -37,6 +41,17 @@ pub struct CallflowViewNode {
pub label: String,
pub kind: String,
pub source_file: Option<String>,
pub scope: CallflowSourceScope,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum CallflowSourceScope {
Production,
Test,
Generated,
Vendor,
Unknown,
}

#[derive(Clone, Debug, Serialize)]
Expand All @@ -47,6 +62,25 @@ pub struct CallflowViewEdge {
pub confidence: String,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallflowCrossSectionCall {
pub source: String,
pub target: String,
pub source_section: String,
pub target_section: String,
pub relation: String,
pub confidence: String,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallflowCoverage {
pub internal: usize,
pub cross_section: usize,
pub unassigned: usize,
}

#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CallflowViewLink {
Expand Down Expand Up @@ -152,27 +186,49 @@ pub fn callflow_view_model(
confidence: confidence(edge.string("confidence")),
})
.collect::<Vec<_>>();
let node_count = nodes.len();
let internal_call_count = edges.len();
view_sections.push(CallflowViewSection {
id: section.id,
name: section.name,
communities: section.communities,
node_count,
internal_call_count,
nodes,
edges,
});
}
let mut link_counts = BTreeMap::<(String, String), usize>::new();
let mut cross_section_calls = Vec::new();
let mut coverage = CallflowCoverage {
internal: 0,
cross_section: 0,
unassigned: 0,
};
for edge in &document.links {
let (Some(source), Some(target)) = (
node_section.get(edge.source.as_str()),
node_section.get(edge.target.as_str()),
) else {
coverage.unassigned += 1;
continue;
};
if source != target {
*link_counts
.entry((source.clone(), target.clone()))
.or_default() += 1;
if source == target {
coverage.internal += 1;
continue;
}
coverage.cross_section += 1;
*link_counts
.entry((source.clone(), target.clone()))
.or_default() += 1;
cross_section_calls.push(CallflowCrossSectionCall {
source: edge.source.clone(),
target: edge.target.clone(),
source_section: source.clone(),
target_section: target.clone(),
relation: edge.string("relation"),
confidence: confidence(edge.string("confidence")),
});
}
let overview_links = link_counts
.into_iter()
Expand Down Expand Up @@ -205,6 +261,8 @@ pub fn callflow_view_model(
title: format!("{} — Architecture Flow", options.project_name),
sections: view_sections,
overview_links,
cross_section_calls,
coverage,
report_highlights,
statistics: CallflowStatistics {
nodes: document.nodes.len(),
Expand Down Expand Up @@ -247,10 +305,55 @@ fn view_node(node: &NodeRecord) -> CallflowViewNode {
kind
}
},
scope: source_scope(&source_file),
source_file: (!source_file.is_empty()).then_some(source_file),
}
}

fn source_scope(source_file: &str) -> CallflowSourceScope {
if source_file.trim().is_empty() {
return CallflowSourceScope::Unknown;
}
let normalized = source_file.replace('\\', "/").to_ascii_lowercase();
let segments = normalized.split('/').collect::<Vec<_>>();
let filename = segments.last().copied().unwrap_or_default();
if segments
.iter()
.any(|segment| matches!(*segment, "test" | "tests" | "testing" | "__tests__"))
|| filename.starts_with("test_")
|| filename.contains("_test.")
|| filename.contains(".test.")
|| filename.contains(".spec.")
{
return CallflowSourceScope::Test;
}
if segments.iter().any(|segment| {
matches!(
*segment,
"generated" | "gen" | "dist" | "build" | "target" | ".next"
)
}) || filename.contains(".generated.")
|| filename.contains("_generated.")
{
return CallflowSourceScope::Generated;
}
if segments.iter().any(|segment| {
matches!(
*segment,
"vendor"
| "third_party"
| "third-party"
| "node_modules"
| ".venv"
| "venv"
| "site-packages"
)
}) {
return CallflowSourceScope::Vendor;
}
CallflowSourceScope::Production
}

fn confidence(value: String) -> String {
match value.to_ascii_lowercase().as_str() {
"inferred" => "inferred",
Expand Down
6 changes: 3 additions & 3 deletions crates/compass-output/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ pub use callflow::{
derive_callflow_sections, write_callflow_html,
};
pub use callflow_model::{
CALLFLOW_VIEWER_SCHEMA, CallflowProvenance, CallflowStatistics, CallflowViewEdge,
CallflowViewLink, CallflowViewModel, CallflowViewNode, CallflowViewSection,
callflow_view_model,
CALLFLOW_VIEWER_SCHEMA, CallflowCoverage, CallflowCrossSectionCall, CallflowProvenance,
CallflowSourceScope, CallflowStatistics, CallflowViewEdge, CallflowViewLink, CallflowViewModel,
CallflowViewNode, CallflowViewSection, callflow_view_model,
};
pub use canvas::{CanvasOptions, canvas_document, write_canvas};
pub use cql::{render_cql_json, render_cql_jsonl, render_cql_table};
Expand Down
140 changes: 140 additions & 0 deletions crates/compass-output/tests/callflow_model.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use std::collections::BTreeMap;
use std::error::Error;

use compass_model::GraphDocument;
use compass_output::{
CALLFLOW_VIEWER_SCHEMA, CallflowOptions, CallflowSection, callflow_view_model,
};
use serde_json::json;

#[test]
fn complete_model_preserves_cross_section_calls_and_edge_coverage() -> Result<(), Box<dyn Error>> {
let document: GraphDocument = serde_json::from_value(json!({
"graph": {},
"nodes": [
{"id":"api","label":"api","community":0,"source_file":"src/api.rs"},
{"id":"helper","label":"helper","community":0,"source_file":"src/helper.rs"},
{"id":"store","label":"store","community":1,"source_file":"src/store.rs"},
{"id":"orphan","label":"orphan","source_file":"src/orphan.rs"}
],
"links": [
{"source":"api","target":"helper","relation":"calls","confidence":"EXTRACTED"},
{"source":"api","target":"store","relation":"calls","confidence":"INFERRED"},
{"source":"orphan","target":"api","relation":"references","confidence":"AMBIGUOUS"}
]
}))?;
let communities = BTreeMap::from([
(0, vec!["api".to_owned(), "helper".to_owned()]),
(1, vec!["store".to_owned()]),
]);
let sections = vec![
CallflowSection {
id: "overview".to_owned(),
name: "Overview".to_owned(),
communities: vec![],
},
CallflowSection {
id: "api".to_owned(),
name: "API".to_owned(),
communities: vec!["0".to_owned()],
},
CallflowSection {
id: "storage".to_owned(),
name: "Storage".to_owned(),
communities: vec!["1".to_owned()],
},
];

let model = callflow_view_model(
&document,
&communities,
&CallflowOptions {
sections: Some(&sections),
project_name: "Fixture",
..CallflowOptions::default()
},
)?;

assert_eq!(model.schema, CALLFLOW_VIEWER_SCHEMA);
assert_eq!(model.coverage.internal, 1);
assert_eq!(model.coverage.cross_section, 1);
assert_eq!(model.coverage.unassigned, 1);
assert_eq!(
model.coverage.internal + model.coverage.cross_section + model.coverage.unassigned,
document.links.len()
);
assert_eq!(model.cross_section_calls.len(), 1);
let call = &model.cross_section_calls[0];
assert_eq!(call.source, "api");
assert_eq!(call.target, "store");
assert_eq!(call.source_section, "api");
assert_eq!(call.target_section, "storage");
assert_eq!(call.relation, "calls");
assert_eq!(call.confidence, "inferred");
assert_eq!(model.sections[1].node_count, 2);
assert_eq!(model.sections[1].internal_call_count, 1);
Ok(())
}

#[test]
fn source_scopes_are_classified_without_discarding_nodes() -> Result<(), Box<dyn Error>> {
let document: GraphDocument = serde_json::from_value(json!({
"graph": {},
"nodes": [
{"id":"prod","community":0,"source_file":"src/lib.rs"},
{"id":"test","community":0,"source_file":"tests/test_api.py"},
{"id":"generated","community":0,"source_file":"generated/schema.rs"},
{"id":"vendor","community":0,"source_file":"vendor/pkg/lib.rs"},
{"id":"unknown","community":0}
],
"links": []
}))?;
let communities = BTreeMap::from([(
0,
vec![
"prod".to_owned(),
"test".to_owned(),
"generated".to_owned(),
"vendor".to_owned(),
"unknown".to_owned(),
],
)]);
let sections = vec![CallflowSection {
id: "core".to_owned(),
name: "Core".to_owned(),
communities: vec!["0".to_owned()],
}];

let model = callflow_view_model(
&document,
&communities,
&CallflowOptions {
sections: Some(&sections),
..CallflowOptions::default()
},
)?;
let scopes = model.sections[1]
.nodes
.iter()
.map(|node| {
(
node.id.as_str(),
serde_json::to_value(&node.scope)
.expect("scope serializes")
.as_str()
.expect("scope is text")
.to_owned(),
)
})
.collect::<BTreeMap<_, _>>();

assert_eq!(scopes.get("prod").map(String::as_str), Some("production"));
assert_eq!(scopes.get("test").map(String::as_str), Some("test"));
assert_eq!(
scopes.get("generated").map(String::as_str),
Some("generated")
);
assert_eq!(scopes.get("vendor").map(String::as_str), Some("vendor"));
assert_eq!(scopes.get("unknown").map(String::as_str), Some("unknown"));
Ok(())
}
Loading
Loading