Skip to main content

parallax_mirror/
lib.rs

1//! Parallax Mirror — continuous cross-language synchronization.
2//!
3//! Links a source project to a target project via a semantic model, then
4//! incrementally syncs changes without full remigration.
5
6#![deny(unsafe_code)]
7#![allow(missing_docs)]
8
9mod diff;
10mod differential;
11mod history;
12mod identity;
13mod link;
14mod merge;
15mod ownership;
16mod policy;
17mod status;
18mod sync;
19
20pub use diff::{ChangeKind, SemanticChange, SemanticDiff};
21pub use differential::{DifferentialCase, DifferentialResult, DifferentialRunner};
22pub use history::{HistoryEntry, SyncHistory};
23pub use identity::SemanticId;
24pub use link::{LinkedProject, SemanticMapping, LINK_DIR};
25pub use merge::{MergeDecision, SemanticConflict, SemanticMerge};
26pub use ownership::{ManualRegion, RegionKind, RegionOwnership};
27pub use policy::{LinkPolicy, SyncPolicy};
28pub use status::{DriftKind, LinkStatus, SemanticDrift};
29pub use sync::{sync_check, sync_link, SyncOptions, SyncReport};
30
31use link::LinkedProject as Link;
32use parallax_core::{ErrorCode, ParallaxError, Remediation};
33use parallax_project::{SourceLanguage, TargetLanguage};
34use std::path::{Path, PathBuf};
35
36/// Create a link between source and target projects.
37pub async fn link_projects(
38    source: &Path,
39    target: &Path,
40    policy: SyncPolicy,
41) -> Result<LinkedProject, ParallaxError> {
42    Link::create(source, target, policy).await
43}
44
45/// Load an existing link from either project root (or explicit `.parallax-link`).
46pub fn load_link(path: &Path) -> Result<LinkedProject, ParallaxError> {
47    Link::load(path)
48}
49
50/// High-level status for CLI / JSON.
51pub async fn link_status(path: &Path) -> Result<LinkStatus, ParallaxError> {
52    let link = load_link(path)?;
53    status::compute_status(&link).await
54}
55
56/// Explain a generated location using link source maps + semantic map.
57pub fn explain(path: &Path, location: &str) -> Result<ExplainReport, ParallaxError> {
58    let link = load_link(path)?;
59    let target_root = PathBuf::from(&link.target_root);
60    let entry = parallax_transmute::lookup_origin(&target_root, location).map_err(|e| {
61        e.remediate(Remediation::new(
62            "Ensure the target was produced by Transmute/Mirror and contains .plxmap.json",
63        ))
64    })?;
65    let node = link
66        .semantic_map
67        .iter()
68        .find(|m| {
69            m.target_file.as_deref() == Some(entry.generated_file.as_str())
70                || entry.semantic_node.contains(&m.qualified_name)
71        })
72        .cloned();
73    Ok(ExplainReport {
74        generated_file: entry.generated_file,
75        generated_line: entry.generated_line,
76        original_file: entry.original_file,
77        original_line: entry.original_line,
78        semantic_node: entry.semantic_node,
79        mapping: node,
80        confidence: "HIGH".into(),
81    })
82}
83
84/// Why a target file changed (last sync history + semantic map).
85pub fn why(path: &Path, target_file: &str) -> Result<WhyReport, ParallaxError> {
86    let link = load_link(path)?;
87    let hist = SyncHistory::load(&link.link_dir)?;
88    let last = hist.entries.last().cloned();
89    let related: Vec<_> = link
90        .semantic_map
91        .iter()
92        .filter(|m| {
93            m.target_file
94                .as_ref()
95                .map(|f| {
96                    f.replace('\\', "/") == target_file.replace('\\', "/")
97                        || target_file.ends_with(f.as_str())
98                })
99                .unwrap_or(false)
100        })
101        .cloned()
102        .collect();
103    Ok(WhyReport {
104        target_file: target_file.into(),
105        last_sync: last,
106        related_nodes: related,
107    })
108}
109
110/// Rollback to previous sync snapshot if available.
111pub fn rollback(path: &Path) -> Result<String, ParallaxError> {
112    history::rollback_last(path)
113}
114
115/// Machine-readable explain output.
116#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
117pub struct ExplainReport {
118    pub generated_file: String,
119    pub generated_line: u32,
120    pub original_file: String,
121    pub original_line: u32,
122    pub semantic_node: String,
123    pub mapping: Option<link::SemanticMapping>,
124    pub confidence: String,
125}
126
127/// Why-report.
128#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
129pub struct WhyReport {
130    pub target_file: String,
131    pub last_sync: Option<HistoryEntry>,
132    pub related_nodes: Vec<link::SemanticMapping>,
133}
134
135/// Pair quality tier (honest, based on implemented coverage).
136#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
137#[serde(rename_all = "snake_case")]
138pub enum PairTier {
139    Tier1,
140    Tier2,
141    Experimental,
142    Unsupported,
143}
144
145/// Look up language-pair maturity (delegates to connector catalog).
146pub fn pair_tier(source: &SourceLanguage, target: &TargetLanguage) -> PairTier {
147    use parallax_connectors::PairMaturity;
148    match parallax_connectors::pair_maturity(source.as_str(), target.as_str()) {
149        PairMaturity::Tier1 => PairTier::Tier1,
150        PairMaturity::Tier2 => PairTier::Tier2,
151        PairMaturity::Experimental => PairTier::Experimental,
152        // Scaffold pairs are catalogued but not Mirror-ready yet.
153        PairMaturity::Scaffold | PairMaturity::Unsupported => PairTier::Unsupported,
154    }
155}
156
157pub(crate) fn io_err(e: std::io::Error) -> ParallaxError {
158    ParallaxError::new(ErrorCode::Io, e.to_string()).with_source("parallax-mirror")
159}