pub mod agent_copy;
pub mod agents;
pub mod config_entries;
pub mod context;
pub mod hooks;
pub mod mcp;
mod native_agents;
pub mod skills;
pub mod variants;
pub mod visibility;
pub use native_agents::selective_native_orphan_preserve_paths;
pub(crate) use native_agents::{
NativeAgentLinkMaterializeCtx, RemovedNativeOutput, materialize_native_agents_after_link,
};
use crate::config::AgentEmission;
use crate::diagnostic::DiagnosticCollector;
use crate::error::MarsError;
use crate::model::ReaderIr;
use crate::sync::{
SyncReport, SyncRequest,
apply::{ActionOutcome, ActionTaken},
apply_plan, build_target, check_frozen_gate, create_plan, finalize, sync_targets,
};
use crate::types::MarsContext;
pub fn compile(
ctx: &MarsContext,
ir: ReaderIr,
request: &SyncRequest,
diag: &mut DiagnosticCollector,
) -> Result<SyncReport, MarsError> {
let targeted = build_target(ctx, ir.resolved, ir.local_items, request, diag)?;
let planned = create_plan(ctx, targeted, request, diag)?;
if request.options.frozen {
check_frozen_gate(&planned)?;
}
let applied = apply_plan(ctx, planned, request)?;
let effective_settings = &applied.planned.targeted.resolved.loaded.effective.settings;
let agent_copy_spec = agent_copy::build_agent_copy_spec(
effective_settings.agent_copy.as_ref(),
&effective_settings.managed_targets(),
diag,
);
let agent_surface_policy = agent_surface_policy(
effective_settings.agent_emission.as_ref(),
agent_copy_spec.as_ref(),
ctx.meridian_managed,
);
let mars_dir = ctx.project_root.join(".mars");
let model_aliases =
native_agents::merged_model_aliases_for_native_agents(&applied.planned.targeted.resolved);
let cursor_probe_slugs = native_agents::cached_cursor_probe_slugs_for_native_agents();
let mars_agents = native_agents::scan_mars_agents(&mars_dir, diag);
let config_entry_records =
config_entries::compile_config_entries(ctx, &applied, request.options.dry_run, diag);
let mut synced = sync_targets(ctx, applied, request, agent_surface_policy.clone(), diag);
synced.config_entries = config_entry_records;
let old_lock = &synced.applied.planned.targeted.resolved.loaded.old_lock;
let outcomes = &synced.applied.applied.outcomes;
let native_reconcile_ctx = native_agents::NativeAgentReconcileCtx {
policy: agent_surface_policy.clone(),
project_root: &ctx.project_root,
model_aliases: &model_aliases,
outcomes,
old_lock,
dry_run: request.options.dry_run,
selective_harness_scope: None,
};
let ownership_lock;
let native_ownership_lock = if request.options.dry_run {
old_lock
} else {
ownership_lock = crate::lock::ownership_lock_for_native_emission(
old_lock,
outcomes,
&synced.target_outcomes,
);
&ownership_lock
};
let native_compile_ctx = if matches!(agent_surface_policy, AgentSurfacePolicy::SuppressAll) {
None
} else {
Some(native_agents::NativeAgentCompileCtx {
project_root: &ctx.project_root,
model_aliases: &model_aliases,
cursor_probe_slugs: &cursor_probe_slugs,
old_lock: native_ownership_lock,
harness_scope: None,
options: native_agents::NativeAgentSurfaceCompileOptions {
force: request.options.force,
collision_hint: crate::surface_ownership::CollisionAdoptHint::SyncForce,
dry_run: request.options.dry_run,
},
})
};
(
synced.compiled_native_outputs,
synced.removed_native_outputs,
) = native_agents::run_native_agent_post_sync_lifecycle(
&native_reconcile_ctx,
&agent_surface_policy,
&mars_agents,
native_compile_ctx.as_ref(),
diag,
);
finalize(ctx, synced, request, diag)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AgentSurfacePolicy {
EmitAll,
EmitSelective(agent_copy::AgentCopySpec),
SuppressAll,
}
pub fn agent_surface_policy(
agent_emission: Option<&AgentEmission>,
agent_copy: Option<&agent_copy::AgentCopySpec>,
meridian_managed: bool,
) -> AgentSurfacePolicy {
match agent_emission.unwrap_or(&AgentEmission::Auto) {
AgentEmission::Always => AgentSurfacePolicy::EmitAll,
AgentEmission::Auto if !meridian_managed => AgentSurfacePolicy::EmitAll,
AgentEmission::Auto | AgentEmission::Never => match agent_copy {
Some(spec) if !spec.harnesses.is_empty() => {
AgentSurfacePolicy::EmitSelective(spec.clone())
}
_ => AgentSurfacePolicy::SuppressAll,
},
}
}
pub fn suppress_agent_outcomes(outcomes: &[ActionOutcome]) -> Vec<ActionOutcome> {
outcomes
.iter()
.cloned()
.map(|mut outcome| {
if outcome.item_id.kind == crate::lock::ItemKind::Agent {
outcome.action = ActionTaken::Removed;
}
outcome
})
.collect()
}
pub fn omit_agent_outcomes(outcomes: &[ActionOutcome]) -> Vec<ActionOutcome> {
outcomes
.iter()
.filter(|outcome| outcome.item_id.kind != crate::lock::ItemKind::Agent)
.cloned()
.collect()
}
#[cfg(test)]
mod skill_surface_tests {
use super::*;
use crate::compiler::agents::HarnessKind;
use crate::config::AgentEmission;
use crate::lock::{ItemId, ItemKind};
use crate::sync::apply::{ActionOutcome, ActionTaken};
use crate::types::{DestPath, ItemName};
#[test]
fn native_agent_emission_defaults_to_standalone_auto() {
assert_eq!(
agent_surface_policy(None, None, false),
AgentSurfacePolicy::EmitAll
);
}
#[test]
fn native_agent_emission_auto_suppresses_meridian_managed() {
assert_eq!(
agent_surface_policy(Some(&AgentEmission::Auto), None, true),
AgentSurfacePolicy::SuppressAll
);
}
#[test]
fn native_agent_emission_always_ignores_meridian_managed() {
assert_eq!(
agent_surface_policy(Some(&AgentEmission::Always), None, true),
AgentSurfacePolicy::EmitAll
);
}
#[test]
fn native_agent_emission_never_suppresses_standalone() {
assert_eq!(
agent_surface_policy(Some(&AgentEmission::Never), None, false),
AgentSurfacePolicy::SuppressAll
);
}
#[test]
fn omit_agent_outcomes_drops_agents_only() {
let outcomes = vec![
ActionOutcome {
item_id: ItemId {
kind: ItemKind::Agent,
name: ItemName::from("coder"),
},
dest_path: DestPath::from("agents/coder.md"),
action: ActionTaken::Installed,
source_name: "test-source".into(),
source_checksum: None,
installed_checksum: None,
},
ActionOutcome {
item_id: ItemId {
kind: ItemKind::Skill,
name: ItemName::from("plan"),
},
dest_path: DestPath::from("skills/plan"),
action: ActionTaken::Installed,
source_name: "test-source".into(),
source_checksum: None,
installed_checksum: None,
},
];
let filtered = omit_agent_outcomes(&outcomes);
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].item_id.kind, ItemKind::Skill);
}
#[test]
fn agent_copy_supersedes_meridian_managed_auto() {
let spec = agent_copy::AgentCopySpec {
harnesses: vec![HarnessKind::Claude],
include_fanout: false,
};
assert!(matches!(
agent_surface_policy(Some(&AgentEmission::Auto), Some(&spec), true),
AgentSurfacePolicy::EmitSelective(_)
));
}
#[test]
fn agent_copy_supersedes_never_emission() {
let spec = agent_copy::AgentCopySpec {
harnesses: vec![HarnessKind::Claude],
include_fanout: false,
};
assert!(matches!(
agent_surface_policy(Some(&AgentEmission::Never), Some(&spec), false),
AgentSurfacePolicy::EmitSelective(_)
));
}
#[test]
fn agent_emission_always_takes_precedence_over_agent_copy() {
let spec = agent_copy::AgentCopySpec {
harnesses: vec![HarnessKind::Claude],
include_fanout: false,
};
assert_eq!(
agent_surface_policy(Some(&AgentEmission::Always), Some(&spec), true),
AgentSurfacePolicy::EmitAll
);
}
}