use std::collections::{BTreeSet, HashMap};
use std::io::Write;
use std::path::PathBuf;
use serde::Serialize;
use crate::catalog;
use crate::error::{MindError, Result};
use crate::manifest::Manifest;
use crate::mindfile::MindToml;
use crate::paths::Paths;
use crate::source::{Registry, Source};
#[derive(Serialize)]
struct DumpDoc {
source: DumpSource,
discover: DumpDiscover,
}
#[derive(Serialize)]
struct DumpSource {
description: String,
}
#[derive(Serialize)]
struct DumpDiscover {
sources: Vec<DumpEntry>,
}
#[derive(Serialize)]
struct DumpEntry {
source: String,
#[serde(rename = "namespace", skip_serializing_if = "Option::is_none")]
namespace: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
kind: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
roots: Option<Vec<String>>,
#[serde(rename = "add-roots", skip_serializing_if = "Option::is_none")]
add_roots: Option<Vec<String>>,
#[serde(rename = "flat-skills", skip_serializing_if = "Option::is_none")]
flat_skills: Option<bool>,
#[serde(rename = "pin-ref", skip_serializing_if = "Option::is_none")]
pin_ref: Option<String>,
#[serde(rename = "pin-version", skip_serializing_if = "Option::is_none")]
pin_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
profiles: Option<Vec<String>>,
#[serde(rename = "default-profile", skip_serializing_if = "Option::is_none")]
default_profile: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
install: Option<bool>,
#[serde(rename = "install-items", skip_serializing_if = "Option::is_none")]
install_items: Option<Vec<String>>,
}
pub fn run(
paths: &Paths,
output: Option<PathBuf>,
whole_sources: bool,
ranges: bool,
) -> Result<()> {
if crate::render::ctx().json {
eprintln!("note: --json does not apply to dump; output is always TOML");
}
let registry = Registry::load(paths)?;
let manifest = Manifest::load(paths)?;
let entries = build_entries(paths, ®istry, &manifest, whole_sources, ranges)?;
let doc = DumpDoc {
source: DumpSource {
description: "Generated by `mind dump`. Meld this file to reproduce the recorded \
source set and install selection."
.to_string(),
},
discover: DumpDiscover { sources: entries },
};
let text = toml::to_string(&doc).map_err(|e| MindError::TomlWrite {
path: output.clone().unwrap_or_else(|| PathBuf::from("<stdout>")),
source: e,
})?;
match output {
None => {
let stdout = std::io::stdout();
let mut out = stdout.lock();
out.write_all(text.as_bytes())
.and_then(|_| out.flush())
.map_err(|e| MindError::io("<stdout>", e))
}
Some(path) => {
std::fs::write(&path, &text).map_err(|e| MindError::io(&path, e))
}
}
}
fn build_entries(
paths: &Paths,
registry: &Registry,
manifest: &Manifest,
whole_sources: bool,
ranges: bool,
) -> Result<Vec<DumpEntry>> {
let mut entries = Vec::with_capacity(registry.sources.len());
for source in ®istry.sources {
if source.item_path.is_some() {
match build_link_entry(paths, source, manifest, whole_sources)? {
Some(entry) => entries.push(entry),
None => {
eprintln!(
"note: skipping item link {} (no recorded commit to pin the \
reconstructed URL to)",
source.name
);
}
}
continue;
}
let mut entry = build_entry(paths, source, manifest, whole_sources)?;
if ranges && let crate::source::Pin::Version(vp) = &source.pin {
entry.pin_ref = None;
entry.pin_version = Some(vp.range.clone());
}
entries.push(entry);
}
Ok(entries)
}
fn build_link_entry(
paths: &Paths,
source: &Source,
manifest: &Manifest,
whole_sources: bool,
) -> Result<Option<DumpEntry>> {
let Some(url) = build_link_url(source) else {
return Ok(None);
};
let (install, install_items) = if whole_sources {
(Some(true), None)
} else {
compute_install_directive(paths, source, manifest)?
};
Ok(Some(DumpEntry {
source: url,
namespace: source.as_alias.clone(),
kind: source.item_kind.map(|k| k.as_str().to_string()),
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: source.commit.clone(),
pin_version: None,
profiles: None,
default_profile: None,
install,
install_items,
}))
}
fn build_link_url(source: &Source) -> Option<String> {
let item_path = source.item_path.as_deref()?;
let commit = source.commit.as_deref()?;
let marker = if crate::catalog::is_file_link(item_path) {
"blob"
} else {
"tree"
};
if source.is_local() {
Some(format!(
"file://{}/{marker}/{commit}/{item_path}",
source.url
))
} else {
Some(format!(
"https://{}/{}/{}/{marker}/{commit}/{item_path}",
source.host, source.owner, source.repo
))
}
}
fn build_entry(
paths: &Paths,
source: &Source,
manifest: &Manifest,
whole_sources: bool,
) -> Result<DumpEntry> {
let source_spec = source.url.clone();
let effective_alias = effective_prefix(paths, source);
let roots = source.roots.clone();
let add_roots = source.add_roots.clone();
let flat_skills = source.flat_skills.then_some(true);
let pin_ref = source.commit.clone();
let (install, install_items, profiles, default_profile) = if whole_sources {
(Some(true), None, None, None)
} else if source.is_profile_tracked() {
let (profiles, default_profile, extras) = profile_directive(source, manifest);
(None, extras, Some(profiles), default_profile)
} else {
let (install, install_items) = compute_install_directive(paths, source, manifest)?;
(install, install_items, None, None)
};
Ok(DumpEntry {
source: source_spec,
namespace: effective_alias,
kind: None,
roots,
add_roots,
flat_skills,
pin_ref,
pin_version: None,
profiles,
default_profile,
install,
install_items,
})
}
fn profile_directive(
source: &Source,
manifest: &Manifest,
) -> (Vec<String>, Option<bool>, Option<Vec<String>>) {
let enabled = source.profiles.as_deref().unwrap_or_default();
let default_on = enabled.iter().any(|p| p == crate::profile::DEFAULT);
let profiles: Vec<String> = enabled
.iter()
.filter(|p| *p != crate::profile::DEFAULT)
.cloned()
.collect();
let snapshot: BTreeSet<&str> = source
.profile_snapshot
.as_deref()
.unwrap_or_default()
.iter()
.map(String::as_str)
.collect();
let extras: BTreeSet<String> = manifest
.items
.values()
.filter(|item| item.source == source.name && item.is_explicit())
.map(|item| format!("{}:{}", item.kind.as_str(), item.bare_name))
.filter(|key| !snapshot.contains(key.as_str()))
.collect();
let extras = (!extras.is_empty()).then(|| extras.into_iter().collect());
(profiles, (!default_on).then_some(false), extras)
}
fn effective_prefix(paths: &Paths, source: &Source) -> Option<String> {
if let Some(alias) = &source.alias {
if !alias.is_empty() {
return Some(alias.clone());
}
return None;
}
let clone_root = source.clone_dir(paths);
if let Ok(Some(mt)) = MindToml::load(&clone_root)
&& let Some(prefix) = mt.source.prefix
&& !prefix.is_empty()
{
return Some(prefix);
}
None
}
fn compute_install_directive(
paths: &Paths,
source: &Source,
manifest: &Manifest,
) -> Result<(Option<bool>, Option<Vec<String>>)> {
let mut offered_items = Vec::new();
catalog::scan_source(paths, source, &mut offered_items)?;
let offered: BTreeSet<String> = offered_items
.iter()
.map(|item| format!("{}:{}", item.kind.as_str(), item.name))
.collect();
let installed_for_source: HashMap<String, ()> = manifest
.items
.values()
.filter(|item| item.source == source.name)
.map(|item| {
let bare_ref = format!("{}:{}", item.kind.as_str(), item.bare_name);
(bare_ref, ())
})
.collect();
let installed_and_offered: BTreeSet<String> = offered
.iter()
.filter(|ref_| installed_for_source.contains_key(*ref_))
.cloned()
.collect();
if offered.is_empty() {
return Ok((Some(false), None));
}
if installed_and_offered.is_empty() {
return Ok((Some(false), None));
}
if installed_and_offered == offered {
return Ok((Some(true), None));
}
let items: Vec<String> = installed_and_offered.into_iter().collect();
Ok((None, Some(items)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::ItemKind;
use crate::manifest::InstalledItem;
use crate::source::Source;
fn make_source(name: &str, url: &str, commit: Option<&str>) -> Source {
Source {
name: name.to_string(),
url: url.to_string(),
host: "local".to_string(),
owner: "test".to_string(),
repo: name.split('/').next_back().unwrap_or(name).to_string(),
commit: commit.map(str::to_string),
description: None,
alias: None,
as_alias: None,
pin: crate::source::Pin::default(),
roots: None,
flat_skills: false,
full_history: false,
add_roots: None,
item_path: None,
item_kind: None,
curated_by: None,
origin: None,
plugin_version: None,
install_hooks: vec![],
install_hook: None,
install_hook_commit: None,
profiles: None,
profile_snapshot: None,
whole_snapshot: None,
auto_update: None,
}
}
fn make_installed(kind: ItemKind, name: &str, bare: &str, source: &str) -> InstalledItem {
InstalledItem {
kind,
name: name.to_string(),
bare_name: bare.to_string(),
source: source.to_string(),
commit: "abc".to_string(),
hash: "def".to_string(),
store: "store/path".to_string(),
links: vec![],
description: None,
install_hooks: Vec::new(),
dropped_requires: Vec::new(),
profiles: Vec::new(),
explicit: false,
version: None,
}
}
#[test]
fn filtering_all_installed_yields_install_true() {
let entry = DumpEntry {
source: "/some/path".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(true),
install_items: None,
};
let doc = DumpDoc {
source: DumpSource {
description: "test".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
};
let text = toml::to_string(&doc).unwrap();
assert!(text.contains("install = true"), "must emit install = true");
assert!(
!text.contains("install-items"),
"must NOT emit install-items when install = true"
);
let back: MindToml = toml::from_str(&text)
.unwrap_or_else(|e| panic!("install=true output must parse as MindToml: {e}"));
let ns = &back.discover.unwrap().sources[0];
assert!(ns.install);
assert!(ns.install_items.is_none());
}
#[test]
fn filtering_none_installed_yields_install_false() {
let entry = DumpEntry {
source: "/some/path".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(false),
install_items: None,
};
let doc = DumpDoc {
source: DumpSource {
description: "test".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
};
let text = toml::to_string(&doc).unwrap();
assert!(
text.contains("install = false"),
"must emit install = false"
);
assert!(
!text.contains("install-items"),
"must NOT emit install-items when none installed"
);
let back: MindToml = toml::from_str(&text)
.unwrap_or_else(|e| panic!("install=false output must parse as MindToml: {e}"));
let ns = &back.discover.unwrap().sources[0];
assert!(!ns.install);
assert!(ns.install_items.is_none());
}
#[test]
fn filtering_proper_subset_yields_install_items() {
let items = vec!["agent:dev".to_string(), "skill:review".to_string()];
let entry = DumpEntry {
source: "/some/path".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: None,
install_items: Some(items.clone()),
};
let doc = DumpDoc {
source: DumpSource {
description: "test".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
};
let text = toml::to_string(&doc).unwrap();
assert!(
text.contains("install-items"),
"must emit install-items for subset"
);
assert!(
!text.contains("install = true"),
"must NOT emit install = true when emitting install-items"
);
let back: MindToml = toml::from_str(&text)
.unwrap_or_else(|e| panic!("install_items output must parse as MindToml: {e}"));
let ns = &back.discover.unwrap().sources[0];
assert!(
!ns.install,
"install bool must be false/absent for subset form"
);
let got = ns
.install_items
.as_deref()
.expect("install_items must be present");
assert_eq!(got, &["agent:dev", "skill:review"][..]);
}
#[test]
fn empty_install_items_is_never_emitted() {
let entry = DumpEntry {
source: "/some/path".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(false),
install_items: None, };
let doc = DumpDoc {
source: DumpSource {
description: "test".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
};
let text = toml::to_string(&doc).unwrap();
assert!(
!text.contains("install-items"),
"install_items=[] must never appear; got: {text}"
);
assert!(text.contains("install = false"));
}
#[test]
fn emitted_toml_parses_as_valid_mindtoml() {
let doc = DumpDoc {
source: DumpSource {
description: "Generated by `mind dump`. Meld this file to reproduce the \
recorded source set and install selection."
.to_string(),
},
discover: DumpDiscover {
sources: vec![
DumpEntry {
source: "/path/to/repo".into(),
namespace: Some("pfx".into()),
kind: None,
roots: Some(vec!["packages".into()]),
add_roots: Some(vec!["extra".into()]),
flat_skills: Some(true),
pin_ref: Some("deadbeefdeadbeef1234".into()),
pin_version: None,
profiles: None,
default_profile: None,
install: Some(true),
install_items: None,
},
DumpEntry {
source: "https://github.com/owner/repo".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: None,
install_items: Some(vec!["skill:review".into(), "agent:dev".into()]),
},
],
},
};
let text =
toml::to_string(&doc).unwrap_or_else(|e| panic!("serialization must not fail: {e}"));
let back: MindToml = toml::from_str(&text)
.unwrap_or_else(|e| panic!("emitted TOML must parse as MindToml: {e}\nTOML:\n{text}"));
assert!(
back.source.description.is_some(),
"emitted TOML must carry [source].description"
);
assert!(back.items.is_empty(), "emitted TOML must have no [[items]]");
let disc = back.discover.expect("must have [discover]");
assert_eq!(disc.sources.len(), 2);
let e0 = &disc.sources[0];
assert!(e0.install, "first entry: install must be true");
assert_eq!(e0.namespace.as_deref(), Some("pfx"));
assert_eq!(e0.roots.as_deref(), Some(&["packages".to_string()][..]));
assert!(
text.contains("add-roots = [\"extra\"]"),
"must emit add-roots for a source with recorded --add-root roots: {text}"
);
assert_eq!(
e0.add_roots.as_deref(),
Some(&["extra".to_string()][..]),
"first entry: add_roots must round-trip"
);
assert!(
text.contains("flat-skills = true"),
"must emit flat-skills = true"
);
assert!(
e0.flat_skills,
"first entry: flat_skills must parse back true"
);
assert!(
e0.pin_ref.is_some(),
"pin-ref must be present for pinned entry"
);
let e1 = &disc.sources[1];
assert!(!e1.install, "second entry: install must be false");
assert!(
!e1.flat_skills,
"second entry: flat_skills must parse back false (no key emitted)"
);
assert!(
e1.add_roots.is_none(),
"second entry: add_roots must be None (no key emitted)"
);
let items = e1
.install_items
.as_deref()
.expect("install_items must be present");
assert_eq!(items, &["skill:review", "agent:dev"][..]);
}
#[test]
fn spec_string_round_trips_for_local_path() {
let source = make_source("local/dev/agents", "/home/dev/agents", Some("abc123"));
let spec = source.url.clone();
let parsed = crate::source::parse_spec(&spec)
.unwrap_or_else(|e| panic!("local spec must round-trip: {e}"));
assert_eq!(
parsed.url, source.url,
"local path spec must round-trip through parse_spec"
);
}
#[test]
fn spec_string_round_trips_for_https_url() {
let url = "https://github.com/owner/repo";
let source = make_source("github.com/owner/repo", url, None);
let spec = source.url.clone();
let parsed = crate::source::parse_spec(&spec)
.unwrap_or_else(|e| panic!("https spec must round-trip: {e}"));
assert_eq!(parsed.url, url);
}
#[test]
fn pin_ref_emitted_when_commit_recorded_regardless_of_pin_kind() {
let entry_with_commit = DumpEntry {
source: "/a/b".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: Some("deadbeefdeadbeef".into()),
pin_version: None,
profiles: None,
default_profile: None,
install: Some(true),
install_items: None,
};
let entry_no_commit = DumpEntry {
source: "/a/b".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(false),
install_items: None,
};
let text_with = toml::to_string(&DumpDoc {
source: DumpSource {
description: "t".into(),
},
discover: DumpDiscover {
sources: vec![entry_with_commit],
},
})
.unwrap();
let text_without = toml::to_string(&DumpDoc {
source: DumpSource {
description: "t".into(),
},
discover: DumpDiscover {
sources: vec![entry_no_commit],
},
})
.unwrap();
assert!(
text_with.contains("pin-ref"),
"must emit pin-ref when a commit is recorded: {text_with}"
);
assert!(
text_with.contains("deadbeefdeadbeef"),
"must emit the recorded commit sha as pin-ref: {text_with}"
);
assert!(
!text_with.contains("follow-branch"),
"must NOT emit follow-branch (replaced by pin-ref): {text_with}"
);
assert!(
!text_without.contains("pin-ref"),
"must NOT emit pin-ref when no commit is recorded: {text_without}"
);
let _: MindToml = toml::from_str(&text_with)
.unwrap_or_else(|e| panic!("pinned entry must parse: {e}\n{text_with}"));
let _: MindToml = toml::from_str(&text_without)
.unwrap_or_else(|e| panic!("unpinned entry must parse: {e}\n{text_without}"));
}
#[test]
fn install_items_sorted_deterministically() {
let mut items = [
"skill:review".to_string(),
"agent:dev".to_string(),
"rule:style".to_string(),
];
items.sort();
assert_eq!(items[0], "agent:dev");
assert_eq!(items[1], "rule:style");
assert_eq!(items[2], "skill:review");
}
#[test]
fn emitted_toml_has_no_items_of_its_own() {
let doc = DumpDoc {
source: DumpSource {
description: "Generated by `mind dump`. test".into(),
},
discover: DumpDiscover { sources: vec![] },
};
let text = toml::to_string(&doc).unwrap();
let back: MindToml =
toml::from_str(&text).unwrap_or_else(|e| panic!("empty doc must parse: {e}"));
assert!(back.items.is_empty(), "must have no [[items]]");
assert!(
!back.is_authoritative(),
"empty discover.sources must not be authoritative (only sources, no item globs)"
);
}
#[test]
fn whole_sources_entry_has_install_true() {
let entry = DumpEntry {
source: "/a/b".into(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(true),
install_items: None,
};
let text = toml::to_string(&DumpDoc {
source: DumpSource {
description: "t".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
})
.unwrap();
assert!(text.contains("install = true"));
let back: MindToml = toml::from_str(&text).unwrap();
assert!(back.discover.unwrap().sources[0].install);
}
#[test]
fn dependency_item_treated_like_any_installed_item() {
let item = make_installed(ItemKind::Skill, "pfx:review", "review", "local/dev/agents");
let bare_ref = format!("{}:{}", item.kind.as_str(), item.bare_name);
assert_eq!(bare_ref, "skill:review");
}
#[test]
fn namespace_field_emitted_not_as_key() {
let entry = DumpEntry {
source: "/a/b".into(),
namespace: Some("pfx".into()),
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(true),
install_items: None,
};
let doc = DumpDoc {
source: DumpSource {
description: "t".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
};
let text = toml::to_string(&doc).unwrap();
assert!(
text.contains("namespace = \"pfx\""),
"must emit `namespace = \"pfx\"` (canonical key): {text}"
);
assert!(
!text.contains("as = "),
"must NOT emit the legacy `as = ` key: {text}"
);
let back: MindToml = toml::from_str(&text)
.unwrap_or_else(|e| panic!("namespace output must parse as MindToml: {e}\n{text}"));
let ns = &back.discover.unwrap().sources[0];
assert_eq!(
ns.namespace.as_deref(),
Some("pfx"),
"emitted namespace key must populate NestedSource::namespace"
);
assert!(
ns.alias.is_none(),
"emitted namespace key must NOT populate NestedSource::alias"
);
}
const PSRC: &str = "local/test/prof";
fn tracked(profiles: &[&str], snapshot: &[&str]) -> Source {
let mut s = make_source(PSRC, "/p/prof", Some("abc"));
s.profiles = Some(profiles.iter().map(|p| p.to_string()).collect());
s.profile_snapshot = Some(snapshot.iter().map(|p| p.to_string()).collect());
s
}
fn prof_item(name: &str, profiles: &[&str], explicit: bool) -> InstalledItem {
let mut i = make_installed(ItemKind::Skill, &format!("pfx:{name}"), name, PSRC);
i.profiles = profiles.iter().map(|p| p.to_string()).collect();
i.explicit = explicit;
i
}
fn manifest_of(items: Vec<InstalledItem>) -> Manifest {
let mut m = Manifest::default();
for i in items {
m.items.insert(i.key().into(), i);
}
m
}
fn emit_tracked(
source: &Source,
manifest: &Manifest,
) -> (String, crate::mindfile::NestedSource) {
let (profiles, default_profile, extras) = profile_directive(source, manifest);
let entry = DumpEntry {
source: source.url.clone(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: source.commit.clone(),
pin_version: None,
profiles: Some(profiles),
default_profile,
install: None,
install_items: extras,
};
let text = toml::to_string(&DumpDoc {
source: DumpSource {
description: "t".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
})
.unwrap();
let back: MindToml = toml::from_str(&text)
.unwrap_or_else(|e| panic!("profile entry must parse as MindToml: {e}\n{text}"));
let ns = back.discover.unwrap().sources.remove(0);
(text, ns)
}
#[test]
fn tracked_source_with_default_enabled_emits_profiles_without_default() {
let s = tracked(&["default", "full"], &["skill:a", "skill:b"]);
let m = manifest_of(vec![
prof_item("a", &["default", "full"], false),
prof_item("b", &["full"], false),
]);
let (text, ns) = emit_tracked(&s, &m);
assert_eq!(ns.profiles.as_deref(), Some(&["full".to_string()][..]));
assert_eq!(ns.default_profile, None, "default enabled: key omitted");
assert!(ns.install_items.is_none(), "no explicit extras: {text}");
assert!(!ns.install, "never install = true with profiles: {text}");
assert!(!text.contains("install = true"), "{text}");
ns.validate(std::path::Path::new("mind.toml"))
.expect("valid entry");
}
#[test]
fn tracked_source_with_default_disabled_emits_default_profile_false() {
let s = tracked(&["full"], &["skill:a"]);
let m = manifest_of(vec![prof_item("a", &["full"], false)]);
let (text, ns) = emit_tracked(&s, &m);
assert_eq!(ns.profiles.as_deref(), Some(&["full".to_string()][..]));
assert_eq!(ns.default_profile, Some(false), "{text}");
}
#[test]
fn tracked_source_with_only_default_emits_empty_profiles() {
let s = tracked(&["default"], &["skill:a"]);
let m = manifest_of(vec![prof_item("a", &["default"], false)]);
let (_, ns) = emit_tracked(&s, &m);
assert_eq!(ns.profiles.as_deref(), Some(&[][..]));
assert_eq!(ns.default_profile, None);
}
#[test]
fn tracked_source_emits_explicit_items_outside_the_snapshot() {
let s = tracked(&["default"], &["skill:member", "skill:forgotten-member"]);
let m = manifest_of(vec![
prof_item("member", &["default"], false),
prof_item("explicit-member", &["default"], true),
prof_item("extra", &[], false),
prof_item("extra2", &["default"], true),
{
let mut o = prof_item("other", &[], false);
o.source = "local/test/other".into();
o
},
]);
let mut s = s;
s.profile_snapshot
.as_mut()
.unwrap()
.push("skill:explicit-member".into());
let (text, ns) = emit_tracked(&s, &m);
assert_eq!(
ns.install_items.as_deref(),
Some(&["skill:extra".to_string(), "skill:extra2".to_string()][..]),
"{text}"
);
assert!(!text.contains("install = true"), "{text}");
}
#[test]
fn untracked_source_is_not_given_profile_keys() {
let s = make_source(PSRC, "/p/prof", Some("abc"));
assert!(!s.is_profile_tracked());
let entry = DumpEntry {
source: s.url.clone(),
namespace: None,
kind: None,
roots: None,
add_roots: None,
flat_skills: None,
pin_ref: None,
pin_version: None,
profiles: None,
default_profile: None,
install: Some(true),
install_items: None,
};
let text = toml::to_string(&DumpDoc {
source: DumpSource {
description: "t".into(),
},
discover: DumpDiscover {
sources: vec![entry],
},
})
.unwrap();
assert!(!text.contains("profiles"), "{text}");
assert!(!text.contains("default-profile"), "{text}");
}
#[test]
fn dump_8_empty_registry_produces_valid_super_source() {
let doc = DumpDoc {
source: DumpSource {
description: "Generated by `mind dump`. Meld this file to reproduce the \
recorded source set and install selection."
.to_string(),
},
discover: DumpDiscover { sources: vec![] },
};
let text = toml::to_string(&doc).unwrap();
let back: MindToml =
toml::from_str(&text).unwrap_or_else(|e| panic!("empty-registry doc must parse: {e}"));
let disc = back.discover.expect("must have [discover]");
assert!(disc.sources.is_empty(), "sources must be empty");
}
fn make_link_source(
host: &str,
owner: &str,
repo: &str,
url: &str,
item_path: &str,
commit: Option<&str>,
) -> Source {
let mut s = make_source(&format!("{host}/{owner}/{repo}"), url, commit);
s.host = host.to_string();
s.owner = owner.to_string();
s.repo = repo.to_string();
s.item_path = Some(item_path.to_string());
s
}
const SHA: &str = "0123456789abcdef0123456789abcdef01234567";
#[test]
fn link_url_reconstructs_and_reparses_for_a_remote_source() {
let source = make_link_source(
"github.com",
"acme",
"repo",
"https://github.com/acme/repo",
"skills/foo",
Some(SHA),
);
let url = build_link_url(&source).expect("commit + item_path present");
assert_eq!(
url,
format!("https://github.com/acme/repo/tree/{SHA}/skills/foo")
);
let reparsed = crate::source::parse_spec(&url)
.unwrap_or_else(|e| panic!("reconstructed link URL must reparse: {e}"));
assert_eq!(reparsed.name, "github.com/acme/repo#skills/foo");
assert_eq!(reparsed.item_path.as_deref(), Some("skills/foo"));
assert_eq!(reparsed.pin, crate::source::Pin::Ref(SHA.to_string()));
}
#[test]
fn link_url_reconstructs_and_reparses_for_a_local_file_source() {
let source = make_link_source(
"local",
"dev",
"agents",
"/home/me/dev/agents",
"skills/foo",
Some(SHA),
);
let url = build_link_url(&source).expect("commit + item_path present");
assert_eq!(
url,
format!("file:///home/me/dev/agents/tree/{SHA}/skills/foo")
);
let reparsed = crate::source::parse_spec(&url)
.unwrap_or_else(|e| panic!("reconstructed file link URL must reparse: {e}"));
assert_eq!(reparsed.host, "local");
assert_eq!(reparsed.name, "local/dev/agents#skills/foo");
assert_eq!(reparsed.url, "/home/me/dev/agents");
assert_eq!(reparsed.item_path.as_deref(), Some("skills/foo"));
}
#[test]
fn link_url_uses_https_even_when_recorded_url_is_ssh_form() {
let source = make_link_source(
"github.com",
"o",
"r",
"git@github.com:o/r",
"skills/foo",
Some(SHA),
);
let url = build_link_url(&source).expect("commit + item_path present");
assert_eq!(
url,
format!("https://github.com/o/r/tree/{SHA}/skills/foo"),
"must build from host/owner/repo, not the SSH-rewritten url: {url}"
);
let reparsed = crate::source::parse_spec(&url)
.unwrap_or_else(|e| panic!("reconstructed https link URL must reparse: {e}"));
assert_eq!(reparsed.host, "github.com");
assert_eq!(reparsed.owner, "o");
assert_eq!(reparsed.repo, "r");
assert_eq!(reparsed.item_path.as_deref(), Some("skills/foo"));
assert_eq!(reparsed.name, "github.com/o/r#skills/foo");
}
#[test]
fn link_url_is_none_without_a_recorded_commit() {
let source = make_link_source(
"github.com",
"acme",
"repo",
"https://github.com/acme/repo",
"skills/foo",
None,
);
assert!(
build_link_url(&source).is_none(),
"no recorded commit must yield None, not a guessed ref"
);
}
#[test]
fn link_entry_namespace_carries_the_identity_alias_not_the_display_prefix() {
let mut source = make_link_source(
"github.com",
"acme",
"repo",
"https://github.com/acme/repo",
"skills/foo",
Some(SHA),
);
source.as_alias = Some("fork".into());
source.alias = Some("displayonly".into());
let url = build_link_url(&source).unwrap();
assert_eq!(source.commit.clone(), Some(SHA.to_string()));
assert_eq!(source.as_alias.as_deref(), Some("fork"));
let mut reparsed = crate::source::parse_spec(&url).unwrap();
reparsed.apply_alias(source.as_alias.clone());
assert_eq!(reparsed.name, "github.com/acme/repo#skills/foo@fork");
}
}