use std::fs;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use serde::{Deserialize, Serialize};
use crate::setup_core::digest;
use crate::harness_runtime::facts::Harness;
use crate::provider_v3::{Error, Result, WireReason};
pub const CATALOG_DIRECTORY: &str = "setups";
pub const SETUP_MANIFEST: &str = "setup.json";
pub const SETUP_PAYLOAD: &str = "home";
pub const SETUP_SCHEMA: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SetupManifest {
pub schema_version: u32,
pub id: String,
pub description: String,
#[serde(default)]
pub sources: Vec<String>,
}
pub const UNIVERSAL_SETUPS: &[&str] = &["baseline", "full-auto", "minimal"];
#[must_use]
pub fn asymmetric(setups: &[Setup]) -> Vec<String> {
let mut found = Vec::new();
for required in UNIVERSAL_SETUPS {
if !setups.iter().any(|setup| setup.manifest.id == *required) {
found.push(format!("this harness offers no {required} setup"));
}
}
for (index, setup) in setups.iter().enumerate() {
for other in &setups[index + 1..] {
if setup.definition_digest == other.definition_digest {
found.push(format!(
"{} and {} are the same bytes, so one of them is a posture in name only",
setup.manifest.id, other.manifest.id
));
}
}
}
found
}
const FROZEN_ESTATE: &[&str] = &[
"nddev_claude_cli",
"nddev_codex_cli",
"nddev_cursor_cli",
"nddev_grok_cli",
"nddev_opencode_cli",
"nddev_pi_cli",
"nddev-antigravity-cli-app",
"nddev-claude-app",
"nddev-codex-app",
"nddev-cursor-cli-app",
"nddev-grok-build-app",
"nddev-opencode-app",
"nddev-pi-app",
"nddev-harnesses",
"rldyour-ai-cli-tools",
];
#[must_use]
pub fn misdirecting(provider_id: &str, setups: &[Setup]) -> Vec<String> {
let mut found = Vec::new();
for setup in setups {
for (relative, path) in files_by_path(&setup.payload) {
let Ok(text) = std::fs::read_to_string(&path) else {
continue;
};
let where_it_is = format!("{}/{relative}", setup.manifest.id);
for name in FROZEN_ESTATE {
if text.contains(name) {
found.push(format!(
"{where_it_is} names {name}, which is the program that came before \
this one and answers none of its commands"
));
}
}
for line in text.lines() {
let Some(at) = line.find(provider_id) else {
continue;
};
let before = line[..at].trim_end_matches(' ');
let invoked = before.is_empty()
|| before.ends_with('`')
|| before.ends_with('$')
|| before.ends_with('>');
if !invoked {
continue;
}
let rest = &line[at + provider_id.len()..];
let Some(verb) = rest.split_whitespace().next() else {
continue;
};
let verb = verb.trim_matches(|c: char| !c.is_ascii_alphanumeric() && c != '-');
if verb.is_empty() || verb.starts_with('-') {
continue;
}
let human = crate::harness_runtime::human::VERBS.contains(&verb);
let wire = crate::provider_v3::vocabulary::Command::parse(verb).is_some();
if !human && !wire && !provider_id.contains(verb) {
found.push(format!(
"{where_it_is} tells a reader to run `{provider_id} {verb}`, \
which this binary answers on neither its human nor its wire surface"
));
}
}
}
}
found.sort();
found.dedup();
found
}
fn files_by_path(root: &std::path::Path) -> Vec<(String, std::path::PathBuf)> {
let mut found = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(directory) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&directory) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if let Ok(relative) = path.strip_prefix(root) {
found.push((
relative
.components()
.map(|part| part.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/"),
path,
));
}
}
}
found
}
fn files_under(root: &std::path::Path) -> Vec<String> {
let mut found = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(directory) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&directory) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if let Ok(relative) = path.strip_prefix(root) {
found.push(
relative
.components()
.map(|part| part.as_os_str().to_string_lossy().into_owned())
.collect::<Vec<_>>()
.join("/"),
);
}
}
}
found
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Examined {
pub problems: Vec<String>,
pub entry_points: usize,
}
#[must_use]
pub fn undescribed(setups: &[Setup]) -> Examined {
let mut found = Vec::new();
let mut entry_points = 0_usize;
for setup in setups {
for name in files_under(&setup.payload) {
if !is_entry_point(&name) {
continue;
}
entry_points += 1;
let Ok(text) = std::fs::read_to_string(setup.payload.join(&name)) else {
found.push(format!("{} cannot read {name:?}", setup.manifest.id));
continue;
};
for key in ["name", "description"] {
if !frontmatter_names(&text, key) {
found.push(format!(
"{} ships {name:?} with no `{key}` in its frontmatter, and a component \
the product cannot describe is one the model cannot choose",
setup.manifest.id
));
}
}
}
for name in files_under(&setup.payload) {
if !is_a_plugin_manifest(&name) {
continue;
}
let Ok(text) = std::fs::read_to_string(setup.payload.join(&name)) else {
found.push(format!("{} cannot read {name:?}", setup.manifest.id));
continue;
};
let Ok(manifest) = serde_json::from_str::<serde_json::Value>(&text) else {
found.push(format!(
"{} ships {name:?} and it is not JSON",
setup.manifest.id
));
continue;
};
for key in ["name", "description"] {
let named = manifest
.get(key)
.and_then(serde_json::Value::as_str)
.is_some_and(|value| !value.trim().is_empty());
if !named {
found.push(format!(
"{} ships {name:?} with no `{key}`, and a component the product \
cannot describe is one the model cannot choose",
setup.manifest.id
));
}
}
}
}
Examined {
problems: found,
entry_points,
}
}
fn is_a_plugin_manifest(relative: &str) -> bool {
let parts: Vec<&str> = relative.split('/').collect();
let vendor_prefixed = matches!(
parts.as_slice(),
[.., directory, "plugin.json"]
if directory.starts_with('.') && directory.ends_with("-plugin")
);
vendor_prefixed || parts.last().is_some_and(|leaf| *leaf == "plugin.json")
}
#[must_use]
pub fn dangling_references(setups: &[Setup]) -> Vec<String> {
let mut found = Vec::new();
for setup in setups {
let files = files_under(&setup.payload);
for (relative, path) in files_by_path(&setup.payload) {
let Ok(text) = std::fs::read_to_string(&path) else {
continue;
};
let here = relative.rsplit_once('/').map_or("", |(dir, _)| dir);
for quoted in text.split('`').skip(1).step_by(2) {
let named = quoted.trim();
let is_document = std::path::Path::new(named)
.extension()
.and_then(std::ffi::OsStr::to_str)
.is_some_and(|e| e.eq_ignore_ascii_case("md"));
if !is_document || !named.contains('/') || named.starts_with('/') {
continue;
}
if named.contains("://") || named.contains(' ') {
continue;
}
if named.contains('*')
|| named.contains('<')
|| named.contains('>')
|| named.contains('{')
|| named.contains('}')
|| named.starts_with('~')
|| named.starts_with('.')
{
continue;
}
let root = named.split('/').next().unwrap_or_default();
if !root.is_empty()
&& root
.chars()
.all(|c| c.is_ascii_uppercase() || c == '_' || c.is_ascii_digit())
{
continue;
}
let _ = here;
if !files
.iter()
.any(|f| f == named || f.ends_with(&format!("/{named}")))
{
found.push(format!(
"{} ships {relative:?}, which sends a reader to {named:?}, and this \
setup carries no such file",
setup.manifest.id
));
}
}
}
}
found.sort();
found.dedup();
found
}
fn is_a_plugin_root(files: &[String], owner: &str) -> bool {
files.iter().any(|name| {
let Some(rest) = name.strip_prefix(&format!("{owner}/")) else {
return false;
};
match rest.split('/').collect::<Vec<_>>().as_slice() {
["plugin.json"] => true,
[directory, "plugin.json"] => {
directory.starts_with('.') && directory.ends_with("-plugin")
}
_ => false,
}
})
}
#[must_use]
pub fn stranded(setups: &[Setup]) -> Examined {
let mut found = Vec::new();
let mut entry_points = 0_usize;
for setup in setups {
for (relative, path) in files_by_path(&setup.payload) {
if !relative.ends_with("SKILL.md") {
continue;
}
let Some(directory) = path.parent() else {
continue;
};
let Ok(entry) = std::fs::read_to_string(&path) else {
continue;
};
let carried: Vec<(String, std::path::PathBuf)> = files_by_path(directory)
.into_iter()
.filter(|(name, _)| name != "SKILL.md")
.collect();
let named_by = |text: &str, name: &str| {
text.contains(name)
|| std::path::Path::new(name)
.file_name()
.and_then(std::ffi::OsStr::to_str)
.is_some_and(|leaf| text.contains(leaf))
};
let mut hop = String::new();
for (name, at) in &carried {
if named_by(&entry, name)
&& let Ok(text) = std::fs::read_to_string(at)
{
hop.push_str(&text);
}
}
for (name, _) in &carried {
entry_points += 1;
if named_by(&entry, name) || named_by(&hop, name) {
continue;
}
found.push(format!(
"{} ships {name:?} inside a skill, and neither its SKILL.md nor \
anything that routes from it names the file",
setup.manifest.id
));
}
}
}
found.sort();
found.dedup();
Examined {
problems: found,
entry_points,
}
}
#[must_use]
pub fn unreachable_references(setups: &[Setup]) -> Vec<String> {
let mut found = Vec::new();
for setup in setups {
let files = files_under(&setup.payload);
for name in &files {
let parts: Vec<&str> = name.split('/').collect();
let Some(at) = parts.iter().position(|part| *part == "references") else {
continue;
};
if at == 0 {
continue;
}
let owner = parts[..at].join("/");
if is_a_plugin_root(&files, &owner) {
continue;
}
let entry = format!("{owner}/SKILL.md");
if !files.iter().any(|other| other.eq_ignore_ascii_case(&entry)) {
found.push(format!(
"{} ships {name:?} under {owner:?}, which has no SKILL.md, so the \
document is reachable from no entry point",
setup.manifest.id
));
}
}
}
found.sort();
found.dedup();
found
}
fn is_entry_point(relative: &str) -> bool {
let parts: Vec<&str> = relative.split('/').collect();
let Some(leaf) = parts.last() else {
return false;
};
if parts.contains(&"references") {
return false;
}
if leaf.eq_ignore_ascii_case("SKILL.md") {
return true;
}
parts.len() >= 2
&& parts[parts.len() - 2] == "agents"
&& std::path::Path::new(leaf)
.extension()
.and_then(std::ffi::OsStr::to_str)
.is_some_and(|extension| extension.eq_ignore_ascii_case("md"))
}
fn frontmatter_names(text: &str, key: &str) -> bool {
let Some(rest) = text.strip_prefix("---\n") else {
return false;
};
let Some(end) = rest.find("\n---") else {
return false;
};
rest[..end]
.lines()
.any(|line| line.trim_start().starts_with(&format!("{key}:")))
}
#[must_use]
pub fn colliding(setups: &[Setup]) -> Vec<String> {
let mut found = Vec::new();
for setup in setups {
let mut folded: std::collections::BTreeMap<String, String> =
std::collections::BTreeMap::new();
for name in files_under(&setup.payload) {
if let Some(other) = folded.insert(name.to_lowercase(), name.clone())
&& other != name
{
found.push(format!(
"{} ships {name:?} and {other:?}, which differ only in case and are one \
file on macOS and Windows",
setup.manifest.id
));
}
}
}
found
}
#[must_use]
pub fn unsourced(setups: &[Setup]) -> Vec<String> {
let mut found = Vec::new();
for setup in setups {
let document = |name: &String| {
std::path::Path::new(name)
.extension()
.and_then(std::ffi::OsStr::to_str)
.is_some_and(|extension| {
extension.eq_ignore_ascii_case("md") || extension.eq_ignore_ascii_case("mdc")
})
};
let writes_configuration = files_under(&setup.payload)
.iter()
.any(|name| !document(name));
if !writes_configuration {
continue;
}
if setup.manifest.sources.is_empty() {
found.push(format!(
"{} writes a configuration file and names no source for its format",
setup.manifest.id
));
continue;
}
for source in &setup.manifest.sources {
if !source.starts_with("https://") {
found.push(format!(
"{} cites {source:?}, which is not a page anyone can open",
setup.manifest.id
));
}
}
}
found
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Setup {
pub manifest: SetupManifest,
pub payload: PathBuf,
pub definition_digest: String,
pub manifest_digest: String,
pub file_count: u64,
lifeline: Lifeline,
}
#[derive(Debug, Clone, Default)]
struct Lifeline(
#[expect(
dead_code,
reason = "held for its Drop: this is the handle that keeps a materialized \
catalog on disk, and dead-code analysis does not count a \
destructor as a use"
)]
Option<Arc<Materialized>>,
);
impl PartialEq for Lifeline {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl Eq for Lifeline {}
#[derive(Debug)]
struct Materialized {
root: PathBuf,
}
impl Materialized {
fn write(harness: &Harness) -> Option<Self> {
static NEXT: AtomicU32 = AtomicU32::new(0);
let root = std::env::temp_dir().join(format!(
"{}-{}-{}",
harness.provider_id,
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).ok()?;
let held = Self { root };
for (relative, bytes) in harness.embedded_setups {
let path = held.root.join(relative);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).ok()?;
}
fs::write(&path, bytes).ok()?;
}
Some(held)
}
fn path(&self) -> &Path {
&self.root
}
}
impl Drop for Materialized {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.root);
}
}
#[derive(Debug, Clone)]
pub struct Catalog {
root: PathBuf,
lifeline: Lifeline,
}
impl Catalog {
#[must_use]
pub fn at(root: impl Into<PathBuf>) -> Self {
Self {
root: root.into(),
lifeline: Lifeline::default(),
}
}
#[must_use]
pub fn discover(harness: &Harness) -> Option<Self> {
Self::on_disk(harness).or_else(|| Self::embedded(harness))
}
#[must_use]
fn on_disk(harness: &Harness) -> Option<Self> {
let variable = format!(
"{}_SETUP_CATALOG",
harness.provider_id.to_uppercase().replace('-', "_")
);
if let Ok(explicit) = std::env::var(&variable) {
let path = PathBuf::from(explicit);
return path.is_dir().then_some(Self::at(path));
}
let mut roots: Vec<PathBuf> = Vec::new();
if let Ok(executable) = std::env::current_exe()
&& let Some(directory) = executable.parent()
{
roots.push(directory.to_path_buf());
let mut walk = directory;
for _ in 0..3 {
let Some(up) = walk.parent() else { break };
roots.push(up.to_path_buf());
walk = up;
}
}
if let Ok(cwd) = std::env::current_dir() {
roots.push(cwd);
}
roots
.into_iter()
.flat_map(|root| {
let base = root.join(CATALOG_DIRECTORY);
[base.join(harness.harness_id), base]
})
.find(|path| path.is_dir() && Self::holds_a_setup(path))
.map(Self::at)
}
#[must_use]
fn embedded(harness: &Harness) -> Option<Self> {
if harness.embedded_setups.is_empty() {
return None;
}
let root = Materialized::write(harness)?;
let path = root.path().to_path_buf();
Some(Self {
root: path,
lifeline: Lifeline(Some(Arc::new(root))),
})
}
fn holds_a_setup(path: &Path) -> bool {
let Ok(read) = fs::read_dir(path) else {
return false;
};
read.flatten()
.any(|entry| entry.path().join(SETUP_MANIFEST).is_file())
}
#[must_use]
pub fn root(&self) -> &Path {
&self.root
}
pub fn list(&self) -> Result<Vec<Setup>> {
let read = match fs::read_dir(&self.root) {
Ok(read) => read,
Err(source) if source.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
Err(source) => {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"cannot list the setup catalog at {}: {source}",
self.root.display()
),
));
}
};
let mut setups = Vec::new();
for entry in read.flatten() {
let path = entry.path();
if !path.is_dir() {
continue;
}
if let Ok(setup) = read_setup(&path, &self.lifeline) {
setups.push(setup);
}
}
setups.sort_by(|left, right| left.manifest.id.cmp(&right.manifest.id));
Ok(setups)
}
pub fn get(&self, id: &str) -> Result<Setup> {
let available = self.list()?;
available
.iter()
.find(|setup| setup.manifest.id == id)
.cloned()
.ok_or_else(|| {
let names: Vec<&str> = available
.iter()
.map(|setup| setup.manifest.id.as_str())
.collect();
Error::refuse(
WireReason::ProviderUnavailable,
if names.is_empty() {
format!("{id:?} is not a setup; this build ships no catalog")
} else {
format!(
"{id:?} is not a setup; this build ships {}",
names.join(", ")
)
},
)
})
}
}
fn read_setup(directory: &Path, lifeline: &Lifeline) -> Result<Setup> {
let manifest_path = directory.join(SETUP_MANIFEST);
let bytes = fs::read(&manifest_path).map_err(|source| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("cannot read {}: {source}", manifest_path.display()),
)
})?;
let manifest: SetupManifest = serde_json::from_slice(&bytes).map_err(|source| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("{} does not parse: {source}", manifest_path.display()),
)
})?;
if manifest.schema_version != SETUP_SCHEMA {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} is schema {} and this build writes {SETUP_SCHEMA}",
manifest_path.display(),
manifest.schema_version
),
));
}
let directory_name = directory.file_name().and_then(|name| name.to_str());
if directory_name != Some(manifest.id.as_str()) {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!(
"{} declares id {:?} but sits in {:?}",
manifest_path.display(),
manifest.id,
directory_name.unwrap_or("?")
),
));
}
let payload = directory.join(SETUP_PAYLOAD);
if !payload.is_dir() {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!("{} has no {SETUP_PAYLOAD} tree", directory.display()),
));
}
Ok(Setup {
definition_digest: digest::of_tree(&payload)?,
manifest_digest: digest::of_bytes(&std::fs::read(&manifest_path).map_err(|error| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("{} cannot be re-read: {error}", manifest_path.display()),
)
})?),
file_count: count_files(&payload)?,
manifest,
payload,
lifeline: lifeline.clone(),
})
}
impl Setup {
pub fn relative_paths(&self) -> Result<Vec<String>> {
let mut found = Vec::new();
let mut stack = vec![self.payload.clone()];
while let Some(current) = stack.pop() {
let read = fs::read_dir(¤t).map_err(|source| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("cannot list {}: {source}", current.display()),
)
})?;
for entry in read.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
continue;
}
let relative = path.strip_prefix(&self.payload).map_err(|_| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("{} escaped the setup payload", path.display()),
)
})?;
let Some(text) = relative.to_str() else {
return Err(Error::refuse(
WireReason::ProviderUnavailable,
format!("{} is not representable as a path", relative.display()),
));
};
found.push(text.replace('\\', "/"));
}
}
found.sort();
Ok(found)
}
pub fn check_within(&self, harness: &Harness) -> Result<()> {
for path in self.relative_paths()? {
if !harness.owns(&path) {
return Err(Error::refuse(
WireReason::UnsupportedNativeSurface,
format!(
"setup {:?} writes {path:?}, which is outside the surface {} owns",
self.manifest.id, harness.provider_id
),
));
}
}
Ok(())
}
}
fn count_files(root: &Path) -> Result<u64> {
let mut total = 0_u64;
let mut stack = vec![root.to_path_buf()];
while let Some(current) = stack.pop() {
let read = fs::read_dir(¤t).map_err(|source| {
Error::refuse(
WireReason::ProviderUnavailable,
format!("cannot list {}: {source}", current.display()),
)
})?;
for entry in read.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else {
total = total.saturating_add(1);
}
}
}
Ok(total)
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::panic)]
use super::*;
fn scratch(name: &str) -> PathBuf {
let base =
std::env::temp_dir().join(format!("harness-catalog-{name}-{}", std::process::id()));
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base).unwrap();
base
}
#[test]
fn a_placeholder_names_a_class_of_file_and_a_relative_path_names_a_document() {
let root = scratch("dangling-placeholders");
write_setup(
&root,
"templates",
&[(
"notes.md",
"See `{workspace}/.agents/skills/{skill_name}/SKILL.md`, \
`<plugin>/agents/*.md` and `~/.cursor/skills/x/SKILL.md`.",
)],
);
let listed = Catalog::at(&root).list().unwrap();
assert_eq!(
dangling_references(&listed),
Vec::<String>::new(),
"a placeholder or an out-of-setup root is not a document this setup carries"
);
let root = scratch("dangling-real");
write_setup(
&root,
"broken",
&[("notes.md", "Read `references/gone.md`.")],
);
let listed = Catalog::at(&root).list().unwrap();
let found = dangling_references(&listed);
assert_eq!(found.len(), 1, "{found:?}");
assert!(found[0].contains("references/gone.md"), "{found:?}");
}
#[test]
fn a_plugin_that_is_a_module_file_needs_neither_guard() {
let root = scratch("plugin-as-module");
write_setup(
&root,
"module-plugin",
&[(
"plugins/nddev-builder.js",
"export const hooks = () => ({});\n",
)],
);
let listed = Catalog::at(&root).list().unwrap();
assert!(
undescribed(&listed).problems.is_empty(),
"a module plugin was asked for fields it does not have: {:?}",
undescribed(&listed).problems
);
assert!(
unreachable_references(&listed).is_empty(),
"a module plugin was walked as though it were a skill: {:?}",
unreachable_references(&listed)
);
}
#[test]
fn a_plugin_manifest_has_to_name_itself() {
let root = scratch("plugin-describes-itself");
write_setup(
&root,
"silent-plugin",
&[(
"skills/tool/.claude-plugin/plugin.json",
"{\"name\": \"tool\", \"version\": \"1.0.0\"}",
)],
);
write_setup(
&root,
"silent-at-the-root",
&[("plugins/tool/plugin.json", "{\"version\": \"1.0.0\"}")],
);
write_setup(
&root,
"speaking-plugin",
&[(
"skills/tool/.claude-plugin/plugin.json",
"{\"name\": \"tool\", \"description\": \"what it is for\"}",
)],
);
let listed = Catalog::at(&root).list().unwrap();
let found = undescribed(&listed).problems;
assert!(
found
.iter()
.any(|p| p.contains("silent-plugin") && p.contains("description")),
"a manifest with no description was not caught: {found:?}"
);
assert!(
found.iter().any(|p| p.contains("silent-at-the-root")),
"the root-manifest shape was not checked: {found:?}"
);
assert!(
found.iter().all(|p| !p.contains("speaking-plugin")),
"a manifest naming both fields was reported anyway: {found:?}"
);
}
#[test]
fn a_plugin_folder_is_not_a_skill_that_lost_its_entry_point() {
let root = scratch("plugin-under-skills");
write_setup(
&root,
"with-manifest",
&[
(
"skills/tool/.claude-plugin/plugin.json",
"{\"name\": \"tool\", \"version\": \"1.0.0\"}",
),
("skills/tool/references/notes.md", "supporting prose"),
],
);
write_setup(
&root,
"without-manifest",
&[("skills/tool/references/notes.md", "supporting prose")],
);
let listed = Catalog::at(&root).list().unwrap();
let found = unreachable_references(&listed);
assert!(
found
.iter()
.all(|problem| !problem.contains("with-manifest")),
"a plugin folder was reported as a skill missing SKILL.md: {found:?}"
);
assert!(
found
.iter()
.any(|problem| problem.contains("without-manifest")),
"the guard stopped catching a skill with no entry point: {found:?}"
);
}
fn write_setup(root: &Path, id: &str, files: &[(&str, &str)]) {
let directory = root.join(id);
fs::create_dir_all(directory.join(SETUP_PAYLOAD)).unwrap();
fs::write(
directory.join(SETUP_MANIFEST),
serde_json::to_vec_pretty(&SetupManifest {
schema_version: SETUP_SCHEMA,
id: id.to_owned(),
description: format!("the {id} setup"),
sources: Vec::new(),
})
.unwrap(),
)
.unwrap();
for (relative, content) in files {
let path = directory.join(SETUP_PAYLOAD).join(relative);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(path, content).unwrap();
}
}
#[test]
fn an_absent_catalog_lists_nothing_rather_than_failing() {
let catalog = Catalog::at(scratch("absent").join("nowhere"));
assert!(catalog.list().unwrap().is_empty());
}
#[test]
fn setups_are_listed_by_identity_with_a_content_digest() {
let root = scratch("list");
write_setup(&root, "safe", &[("AGENTS.md", "# safe\n")]);
write_setup(&root, "full-auto", &[("AGENTS.md", "# full\n")]);
let listed = Catalog::at(&root).list().unwrap();
assert_eq!(
listed
.iter()
.map(|s| s.manifest.id.as_str())
.collect::<Vec<_>>(),
vec!["full-auto", "safe"]
);
assert!(listed[0].definition_digest.starts_with("sha256:"));
assert_ne!(listed[0].definition_digest, listed[1].definition_digest);
assert_eq!(listed[0].file_count, 1);
}
#[test]
fn identity_is_content_so_two_names_over_the_same_bytes_agree() {
let root = scratch("same-bytes");
write_setup(&root, "one", &[("AGENTS.md", "identical\n")]);
write_setup(&root, "two", &[("AGENTS.md", "identical\n")]);
let listed = Catalog::at(&root).list().unwrap();
assert_eq!(listed[0].definition_digest, listed[1].definition_digest);
}
#[test]
fn a_setup_whose_directory_and_declared_id_disagree_is_not_listed() {
let root = scratch("mismatch");
write_setup(&root, "safe", &[("AGENTS.md", "x")]);
fs::rename(root.join("safe"), root.join("renamed")).unwrap();
assert!(Catalog::at(&root).list().unwrap().is_empty());
}
#[test]
fn one_broken_setup_does_not_make_the_others_unlistable() {
let root = scratch("partly-broken");
write_setup(&root, "good", &[("AGENTS.md", "x")]);
fs::create_dir_all(root.join("broken")).unwrap();
fs::write(root.join("broken").join(SETUP_MANIFEST), "{ not json").unwrap();
let listed = Catalog::at(&root).list().unwrap();
assert_eq!(listed.len(), 1);
assert_eq!(listed[0].manifest.id, "good");
}
#[test]
fn a_setup_with_no_payload_tree_is_refused() {
let root = scratch("no-payload");
write_setup(&root, "empty", &[]);
fs::remove_dir_all(root.join("empty").join(SETUP_PAYLOAD)).unwrap();
assert!(Catalog::at(&root).list().unwrap().is_empty());
}
#[test]
fn asking_for_an_unknown_setup_names_the_ones_that_exist() {
let root = scratch("unknown");
write_setup(&root, "safe", &[("AGENTS.md", "x")]);
let error = Catalog::at(&root).get("nope").unwrap_err();
assert!(error.detail().contains("safe"), "{error}");
}
#[test]
fn a_setup_writing_outside_the_declared_surface_is_refused() {
let root = scratch("outside");
write_setup(
&root,
"sneaky",
&[("AGENTS.md", "x"), ("elsewhere.txt", "y")],
);
let setup = Catalog::at(&root).get("sneaky").unwrap();
let harness = crate::harness_runtime::wire::tests_support::TEST;
let error = setup.check_within(&harness).unwrap_err();
assert_eq!(error.reason(), Some(WireReason::UnsupportedNativeSurface));
assert!(error.detail().contains("elsewhere.txt"));
}
#[test]
fn a_setup_inside_the_declared_surface_is_accepted() {
let root = scratch("inside");
write_setup(&root, "fine", &[("AGENTS.md", "x"), ("skills/a.md", "y")]);
let setup = Catalog::at(&root).get("fine").unwrap();
assert!(
setup
.check_within(&crate::harness_runtime::wire::tests_support::TEST)
.is_ok()
);
assert_eq!(setup.file_count, 2);
}
const EMBEDDED: &[(&str, &[u8])] = &[
(
"baseline/setup.json",
br#"{"schema_version":1,"id":"baseline","description":"the baseline setup"}"#,
),
("baseline/home/AGENTS.md", b"# instructions\n"),
("baseline/home/skills/a.md", b"a skill\n"),
];
fn harness_carrying_the_embedded_catalog() -> Harness {
let mut harness = crate::harness_runtime::wire::tests_support::TEST;
harness.embedded_setups = EMBEDDED;
harness
}
#[test]
fn a_setup_outlives_the_embedded_catalog_it_came_from() {
let harness = harness_carrying_the_embedded_catalog();
let setup = Catalog::embedded(&harness)
.unwrap()
.get("baseline")
.unwrap();
assert_eq!(
fs::read_to_string(setup.payload.join("AGENTS.md")).unwrap(),
"# instructions\n",
"the bytes were deleted while a caller still held the path to them"
);
assert_eq!(setup.file_count, 2);
}
#[test]
fn the_embedded_catalog_and_the_same_bytes_on_disk_are_one_setup() {
let root = scratch("embedded-equals-disk");
write_setup(
&root,
"baseline",
&[
("AGENTS.md", "# instructions\n"),
("skills/a.md", "a skill\n"),
],
);
let on_disk = Catalog::at(&root).get("baseline").unwrap();
let harness = harness_carrying_the_embedded_catalog();
let embedded = Catalog::embedded(&harness)
.unwrap()
.get("baseline")
.unwrap();
assert_eq!(
embedded.definition_digest, on_disk.definition_digest,
"the binary and the tree disagree about what the baseline setup is"
);
assert_eq!(embedded.file_count, on_disk.file_count);
}
#[test]
fn a_build_carrying_no_embedded_catalog_finds_none() {
assert!(Catalog::embedded(&crate::harness_runtime::wire::tests_support::TEST).is_none());
}
#[test]
fn two_embedded_catalogs_in_one_process_do_not_share_a_directory() {
let harness = harness_carrying_the_embedded_catalog();
let first = Catalog::embedded(&harness).unwrap();
let second = Catalog::embedded(&harness).unwrap();
assert_ne!(first.root(), second.root());
assert!(first.get("baseline").is_ok());
assert!(second.get("baseline").is_ok());
}
}