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    #[serde(default)]
90    pub declared_business_domains: Vec<CodebaseViewDeclaredBusinessDomain>,
91    pub files: Vec<CodebaseViewFile>,
92    pub symbols: Vec<CodebaseViewSymbol>,
93    pub imports: Vec<CodeImportRecord>,
94    pub calls: Vec<CodebaseViewCall>,
95    pub routes: Vec<CodeRouteRecord>,
96    pub dependencies: Vec<CodebaseViewDependency>,
97    pub feature_flags: Vec<CodeFeatureFlagRecord>,
98    pub truncated: bool,
99}
100
101/// Authored domain row merged ahead of inferred route, flag, and path signals.
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct CodebaseViewDeclaredBusinessDomain {
104    pub id: String,
105    pub name: String,
106    pub source_path: String,
107    pub evidence_id: String,
108}
109
110/// File evidence row in a codebase view snapshot.
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct CodebaseViewFile {
113    pub path: String,
114    pub language_id: String,
115    pub parse_status: String,
116    pub line_count: usize,
117    pub is_generated: bool,
118}
119
120/// Symbol evidence row in a codebase view snapshot.
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
122pub struct CodebaseViewSymbol {
123    pub symbol_snapshot_id: String,
124    pub path: String,
125    pub language_id: String,
126    pub name: String,
127    pub qualified_name: String,
128    pub kind: String,
129    pub line_range: RepositoryCodeRange,
130}
131
132/// Call evidence row with optional resolved target path.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct CodebaseViewCall {
135    pub call: CodeCallRecord,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub callee_path: Option<String>,
138}
139
140/// Dependency evidence row from manifests and lockfiles.
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct CodebaseViewDependency {
143    pub dependency_id: String,
144    pub path: String,
145    pub language_id: String,
146    pub ecosystem: String,
147    pub package_name: String,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub requirement: Option<String>,
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub resolved_version: Option<String>,
152    pub dependency_group: String,
153    pub source_kind: String,
154    pub line_range: RepositoryCodeRange,
155}
156
157/// Graph-derived view node.
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub struct CodebaseViewNode {
160    pub id: String,
161    pub label: String,
162    pub node_kind: String,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub path: Option<String>,
165    pub confidence: f64,
166    pub evidence_ids: Vec<String>,
167}
168
169/// Graph-derived view edge.
170#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171pub struct CodebaseViewEdge {
172    pub id: String,
173    pub source_id: String,
174    pub target_id: String,
175    pub edge_kind: String,
176    pub confidence: f64,
177    pub evidence_ids: Vec<String>,
178}
179
180/// Narrative section derived from graph facts and evidence.
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct CodebaseViewSection {
183    pub id: String,
184    pub title: String,
185    pub narrative: String,
186    pub confidence: f64,
187    pub node_ids: Vec<String>,
188    pub edge_ids: Vec<String>,
189    pub evidence_ids: Vec<String>,
190    #[serde(default, skip_serializing_if = "Vec::is_empty")]
191    pub diagnostics: Vec<String>,
192}
193
194/// Evidence reference backing a node, edge, or section.
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196pub struct CodebaseViewEvidence {
197    pub id: String,
198    pub evidence_kind: String,
199    pub path: String,
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub symbol: Option<String>,
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub line_range: Option<RepositoryCodeRange>,
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub edge_kind: Option<String>,
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub retrieval_layer: Option<CodeRetrievalLayer>,
208    pub detail: String,
209}
210
211/// View derivation budget and truncation metadata.
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
213pub struct CodebaseViewBudget {
214    pub requested_limit: usize,
215    pub snapshot_row_limit: usize,
216    pub snapshot_truncated: bool,
217    pub nodes_truncated: bool,
218    pub edges_truncated: bool,
219    pub sections_truncated: bool,
220    pub evidence_truncated: bool,
221}
222
223impl CodebaseViewBudget {
224    /// Records the bounded work performed while deriving a view.
225    pub const fn new(
226        requested_limit: usize,
227        snapshot_row_limit: usize,
228        snapshot_truncated: bool,
229    ) -> Self {
230        Self {
231            requested_limit,
232            snapshot_row_limit,
233            snapshot_truncated,
234            nodes_truncated: false,
235            edges_truncated: false,
236            sections_truncated: false,
237            evidence_truncated: false,
238        }
239    }
240}