Skip to main content

rpi_cli/
source_info.rs

1//! Resource provenance metadata.
2//!
3//! Port of the upstream `packages/coding-agent/src/core/source-info.ts`. Every
4//! discovered resource (extension, skill, prompt, theme, package file) can carry
5//! where it came from: the owning package source, the scope it applies to, and
6//! whether it is a top-level file or package-contributed. The UI uses this to
7//! explain *why* a given skill or command is present.
8
9use std::path::PathBuf;
10
11/// Where a resource applies (native `SourceScope`).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SourceScope {
14    User,
15    Project,
16    Temporary,
17}
18
19impl SourceScope {
20    pub fn as_str(self) -> &'static str {
21        match self {
22            Self::User => "user",
23            Self::Project => "project",
24            Self::Temporary => "temporary",
25        }
26    }
27}
28
29/// Whether a resource came from a package or sits at the top level.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum SourceOrigin {
32    Package,
33    TopLevel,
34}
35
36impl SourceOrigin {
37    pub fn as_str(self) -> &'static str {
38        match self {
39            Self::Package => "package",
40            Self::TopLevel => "top-level",
41        }
42    }
43}
44
45/// Provenance for a discovered resource (native `SourceInfo`).
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct SourceInfo {
48    pub path: PathBuf,
49    /// e.g. `npm:foo`, `git:…`, `local`.
50    pub source: String,
51    pub scope: SourceScope,
52    pub origin: SourceOrigin,
53    pub base_dir: Option<PathBuf>,
54}
55
56impl SourceInfo {
57    /// Construct from explicit fields (native `createSourceInfo`).
58    pub fn new(
59        path: impl Into<PathBuf>,
60        source: impl Into<String>,
61        scope: SourceScope,
62        origin: SourceOrigin,
63        base_dir: Option<PathBuf>,
64    ) -> Self {
65        Self {
66            path: path.into(),
67            source: source.into(),
68            scope,
69            origin,
70            base_dir,
71        }
72    }
73
74    /// Construct a synthetic source (native `createSyntheticSourceInfo`):
75    /// defaults to `temporary` scope and `top-level` origin.
76    pub fn synthetic(path: impl Into<PathBuf>, source: impl Into<String>) -> Self {
77        Self {
78            path: path.into(),
79            source: source.into(),
80            scope: SourceScope::Temporary,
81            origin: SourceOrigin::TopLevel,
82            base_dir: None,
83        }
84    }
85
86    /// A short `source · scope` label for UI display.
87    pub fn label(&self) -> String {
88        format!("{} · {}", self.source, self.scope.as_str())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn synthetic_defaults() {
98        let info = SourceInfo::synthetic("/x/SKILL.md", "local");
99        assert_eq!(info.scope, SourceScope::Temporary);
100        assert_eq!(info.origin, SourceOrigin::TopLevel);
101        assert_eq!(info.label(), "local · temporary");
102    }
103
104    #[test]
105    fn explicit_fields() {
106        let info = SourceInfo::new(
107            "/p/SKILL.md",
108            "npm:foo",
109            SourceScope::Project,
110            SourceOrigin::Package,
111            Some(PathBuf::from("/p")),
112        );
113        assert_eq!(info.origin.as_str(), "package");
114        assert_eq!(info.base_dir, Some(PathBuf::from("/p")));
115    }
116}