Skip to main content

comindly_core/
model.rs

1//! Comindly core domain model.
2//!
3//! Storage-agnostic types shared by every crate: global symbol identity (SCIP scheme),
4//! symbols, edges, languages, and source ranges. No heavy dependencies — this crate is
5//! the stable contract the rest of the workspace builds on.
6
7use serde::{Deserialize, Serialize};
8
9/// Languages we can parse. Backed by tree-sitter grammars (polyglot from day 1).
10///
11/// `Other` keeps the enum open so an unknown extension degrades gracefully instead of
12/// failing indexing.
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "snake_case")]
15pub enum Language {
16    Python,
17    TypeScript,
18    JavaScript,
19    Go,
20    Rust,
21    Java,
22    Kotlin,
23    Ruby,
24    CSharp,
25    Cpp,
26    C,
27    Php,
28    Scala,
29    Swift,
30    Other(String),
31}
32
33/// What a symbol *is*. Superset of the Python model, widened for polyglot codebases.
34#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[serde(rename_all = "snake_case")]
36pub enum SymbolKind {
37    File,
38    Module,
39    Namespace,
40    Class,
41    Interface,
42    Trait,
43    Enum,
44    Struct,
45    Function,
46    Method,
47    Field,
48    Variable,
49    Constant,
50    Import,
51    TypeAlias,
52    /// A detected multi-step execution flow (comindly-specific higher-order node).
53    Process,
54    /// A cross-service contract: an HTTP route, queue, topic, bucket or table.
55    Contract,
56}
57
58/// Edge kinds between symbols. `cross_repo` on [`Edge`] marks whether an edge spans repos.
59#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60#[serde(rename_all = "snake_case")]
61pub enum EdgeKind {
62    Contains,
63    Imports,
64    Calls,
65    Instantiates,
66    Inherits,
67    Implements,
68    References,
69    Defines,
70    Uses,
71    ParticipatesIn,
72    /// Contract roles: provider side (serves/publishes/writes/defines) and consumer side.
73    Serves,
74    Consumes,
75    Publishes,
76    Subscribes,
77    Reads,
78    Writes,
79}
80
81impl EdgeKind {
82    /// Stable lowercase label for user- and agent-facing output (not Rust `Debug`).
83    pub fn as_str(&self) -> &'static str {
84        match self {
85            EdgeKind::Contains => "contains",
86            EdgeKind::Imports => "imports",
87            EdgeKind::Calls => "calls",
88            EdgeKind::Instantiates => "instantiates",
89            EdgeKind::Inherits => "inherits",
90            EdgeKind::Implements => "implements",
91            EdgeKind::References => "references",
92            EdgeKind::Defines => "defines",
93            EdgeKind::Uses => "uses",
94            EdgeKind::ParticipatesIn => "participates_in",
95            EdgeKind::Serves => "serves",
96            EdgeKind::Consumes => "consumes",
97            EdgeKind::Publishes => "publishes",
98            EdgeKind::Subscribes => "subscribes",
99            EdgeKind::Reads => "reads",
100            EdgeKind::Writes => "writes",
101        }
102    }
103}
104
105impl std::fmt::Display for EdgeKind {
106    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107        f.write_str(self.as_str())
108    }
109}
110
111/// Identifier for a repository within the federated index.
112#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
113pub struct RepoId(pub String);
114
115/// A git commit SHA — indexes are versioned per commit in S3.
116#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
117pub struct Commit(pub String);
118
119/// Globally-unique symbol identity following the SCIP symbol scheme, so identity is stable
120/// and unique **across repos** — the primitive that makes cross-repo `ripple` possible.
121///
122/// Rendered form: `<scheme> <package_manager> <package> <version> <descriptor>`
123/// e.g. `scip-python pip acme 1.4.0 acme/foo/bar().`
124#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
125pub struct GlobalSymbolId {
126    /// Indexer scheme, e.g. `scip-python`, `scip-typescript`, `comindly-treesitter`.
127    pub scheme: String,
128    /// Package manager, e.g. `pip`, `npm`, `cargo`, `.` when local/unmanaged.
129    pub package_manager: String,
130    /// Package name, e.g. `acme`.
131    pub package: String,
132    /// Package version, `.` when unknown.
133    pub version: String,
134    /// SCIP descriptor path, e.g. `acme/foo/bar().`.
135    pub descriptor: String,
136}
137
138impl GlobalSymbolId {
139    /// Render the canonical space-delimited SCIP symbol string.
140    pub fn render(&self) -> String {
141        format!(
142            "{} {} {} {} {}",
143            self.scheme, self.package_manager, self.package, self.version, self.descriptor
144        )
145    }
146
147    /// True when this id refers to a package-managed (potentially cross-repo) symbol
148    /// rather than a purely local one.
149    pub fn is_package_managed(&self) -> bool {
150        self.package_manager != "."
151    }
152}
153
154/// A byte/line/column source position.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156pub struct Position {
157    pub line: u32,
158    pub column: u32,
159}
160
161/// A half-open source range `[start, end)`.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
163pub struct Range {
164    pub start: Position,
165    pub end: Position,
166}
167
168/// A code entity extracted from a source file.
169#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
170pub struct Symbol {
171    pub id: GlobalSymbolId,
172    pub name: String,
173    pub kind: SymbolKind,
174    pub language: Language,
175    pub repo: RepoId,
176    /// Repo-relative path.
177    pub file_path: String,
178    pub range: Range,
179    pub signature: Option<String>,
180    pub docstring: Option<String>,
181    #[serde(default)]
182    pub fingerprint: Option<Fingerprint>,
183}
184
185/// Normalized AST hashes: signature, body, and body with identifiers alpha-renamed (rename-stable).
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
187pub struct Fingerprint {
188    pub sig: u64,
189    pub body: u64,
190    pub shape: u64,
191}
192
193/// A directed relationship between two symbols. Edges may cross repo boundaries once the
194/// link-resolver has bound package-managed references to their definitions.
195#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub struct Edge {
197    pub src: GlobalSymbolId,
198    pub dst: GlobalSymbolId,
199    pub kind: EdgeKind,
200    /// Resolver confidence in `[0.0, 1.0]` (syntactic tree-sitter edges are < 1.0).
201    pub confidence: f32,
202    /// Whether `src` and `dst` live in different repos.
203    pub cross_repo: bool,
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn scip_id_renders_and_detects_managed() {
212        let id = GlobalSymbolId {
213            scheme: "scip-python".into(),
214            package_manager: "pip".into(),
215            package: "acme".into(),
216            version: "1.4.0".into(),
217            descriptor: "acme/foo/bar().".into(),
218        };
219        assert_eq!(id.render(), "scip-python pip acme 1.4.0 acme/foo/bar().");
220        assert!(id.is_package_managed());
221
222        let local = GlobalSymbolId {
223            package_manager: ".".into(),
224            ..id
225        };
226        assert!(!local.is_package_managed());
227    }
228}