Skip to main content

relay_knowledge/domain/code/views/
mod.rs

1//! Defines bounded graph-derived codebase view requests and snapshots.
2
3use serde::{Deserialize, Serialize};
4
5use super::{
6    CodeCallRecord, CodeFeatureFlagRecord, CodeImportRecord, CodeRepositorySelector,
7    CodeRetrievalLayer, CodeRouteRecord, DomainError, FreshnessPolicy, RepositoryCodeRange,
8    error::required_text,
9};
10
11const MAX_CODEBASE_VIEW_CHANGED_PATHS: usize = 200;
12
13/// Deterministic repository understanding view kind.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum CodebaseViewKind {
17    ArchitectureLayers,
18    BusinessDomains,
19    DependencyTour,
20    ProcessFlow,
21    AffectedScope,
22}
23
24impl CodebaseViewKind {
25    /// Stable CLI, API, and MCP representation.
26    pub const fn as_str(self) -> &'static str {
27        match self {
28            Self::ArchitectureLayers => "architecture_layers",
29            Self::BusinessDomains => "business_domains",
30            Self::DependencyTour => "dependency_tour",
31            Self::ProcessFlow => "process_flow",
32            Self::AffectedScope => "affected_scope",
33        }
34    }
35}
36
37/// Request for a graph-derived repository understanding view.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct CodebaseViewRequest {
40    pub repository: CodeRepositorySelector,
41    pub view_kind: CodebaseViewKind,
42    pub freshness_policy: FreshnessPolicy,
43    pub limit: usize,
44    #[serde(default, skip_serializing_if = "Vec::is_empty")]
45    pub changed_paths: Vec<String>,
46}
47
48impl CodebaseViewRequest {
49    /// Validates view inputs and bounds result fan-out.
50    pub fn new(
51        repository: CodeRepositorySelector,
52        view_kind: CodebaseViewKind,
53        freshness_policy: FreshnessPolicy,
54        limit: usize,
55        changed_paths: Vec<String>,
56    ) -> Result<Self, DomainError> {
57        let limit = match limit {
58            1..=100 => limit,
59            0 => return Err(DomainError::invalid("limit", "must be greater than zero")),
60            _ => return Err(DomainError::invalid("limit", "must be 100 or less")),
61        };
62        let changed_paths = changed_paths
63            .into_iter()
64            .map(|path| required_text("changed_path", path))
65            .collect::<Result<Vec<_>, _>>()?;
66        if changed_paths.len() > MAX_CODEBASE_VIEW_CHANGED_PATHS {
67            return Err(DomainError::invalid(
68                "changed_paths",
69                "must contain 200 or fewer entries",
70            ));
71        }
72
73        Ok(Self {
74            repository,
75            view_kind,
76            freshness_policy,
77            limit,
78            changed_paths,
79        })
80    }
81}
82
83#[cfg(test)]
84mod mod_tests;
85
86/// Bounded raw graph rows used to derive codebase views.
87#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
88pub struct CodebaseViewSnapshot {
89    pub files: Vec<CodebaseViewFile>,
90    pub symbols: Vec<CodebaseViewSymbol>,
91    pub imports: Vec<CodeImportRecord>,
92    pub calls: Vec<CodebaseViewCall>,
93    pub routes: Vec<CodeRouteRecord>,
94    pub dependencies: Vec<CodebaseViewDependency>,
95    pub feature_flags: Vec<CodeFeatureFlagRecord>,
96    pub truncated: bool,
97}
98
99/// File evidence row in a codebase view snapshot.
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101pub struct CodebaseViewFile {
102    pub path: String,
103    pub language_id: String,
104    pub parse_status: String,
105    pub line_count: usize,
106    pub is_generated: bool,
107}
108
109/// Symbol evidence row in a codebase view snapshot.
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
111pub struct CodebaseViewSymbol {
112    pub symbol_snapshot_id: String,
113    pub path: String,
114    pub language_id: String,
115    pub name: String,
116    pub qualified_name: String,
117    pub kind: String,
118    pub line_range: RepositoryCodeRange,
119}
120
121/// Call evidence row with optional resolved target path.
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123pub struct CodebaseViewCall {
124    pub call: CodeCallRecord,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub callee_path: Option<String>,
127}
128
129/// Dependency evidence row from manifests and lockfiles.
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct CodebaseViewDependency {
132    pub dependency_id: String,
133    pub path: String,
134    pub language_id: String,
135    pub ecosystem: String,
136    pub package_name: String,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub requirement: Option<String>,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub resolved_version: Option<String>,
141    pub dependency_group: String,
142    pub source_kind: String,
143    pub line_range: RepositoryCodeRange,
144}
145
146/// Graph-derived view node.
147#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148pub struct CodebaseViewNode {
149    pub id: String,
150    pub label: String,
151    pub node_kind: String,
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub path: Option<String>,
154    pub confidence: f64,
155    pub evidence_ids: Vec<String>,
156}
157
158/// Graph-derived view edge.
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct CodebaseViewEdge {
161    pub id: String,
162    pub source_id: String,
163    pub target_id: String,
164    pub edge_kind: String,
165    pub confidence: f64,
166    pub evidence_ids: Vec<String>,
167}
168
169/// Narrative section derived from graph facts and evidence.
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub struct CodebaseViewSection {
172    pub id: String,
173    pub title: String,
174    pub narrative: String,
175    pub confidence: f64,
176    pub node_ids: Vec<String>,
177    pub edge_ids: Vec<String>,
178    pub evidence_ids: Vec<String>,
179    #[serde(default, skip_serializing_if = "Vec::is_empty")]
180    pub diagnostics: Vec<String>,
181}
182
183/// Evidence reference backing a node, edge, or section.
184#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
185pub struct CodebaseViewEvidence {
186    pub id: String,
187    pub evidence_kind: String,
188    pub path: String,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub symbol: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub line_range: Option<RepositoryCodeRange>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    pub edge_kind: Option<String>,
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub retrieval_layer: Option<CodeRetrievalLayer>,
197    pub detail: String,
198}
199
200/// View derivation budget and truncation metadata.
201#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
202pub struct CodebaseViewBudget {
203    pub requested_limit: usize,
204    pub snapshot_row_limit: usize,
205    pub snapshot_truncated: bool,
206    pub nodes_truncated: bool,
207    pub edges_truncated: bool,
208    pub sections_truncated: bool,
209    pub evidence_truncated: bool,
210}
211
212impl CodebaseViewBudget {
213    /// Records the bounded work performed while deriving a view.
214    pub const fn new(
215        requested_limit: usize,
216        snapshot_row_limit: usize,
217        snapshot_truncated: bool,
218    ) -> Self {
219        Self {
220            requested_limit,
221            snapshot_row_limit,
222            snapshot_truncated,
223            nodes_truncated: false,
224            edges_truncated: false,
225            sections_truncated: false,
226            evidence_truncated: false,
227        }
228    }
229}