use crate::common::{ManifestBuilder, TestProject};
use anyhow::Result;
#[tokio::test]
async fn test_exact_version_conflict_blocks_install() -> Result<()> {
let project = TestProject::new().await?;
let source_repo = project.create_source_repo("community").await?;
source_repo.add_resource("agents", "api-designer", "# API Designer v0.0.1").await?;
source_repo.commit_all("Add v0.0.1")?;
source_repo.tag_version("v0.0.1")?;
source_repo.add_resource("agents", "api-designer", "# API Designer v0.0.2").await?;
source_repo.commit_all("Update to v0.0.2")?;
source_repo.tag_version("v0.0.2")?;
let manifest = ManifestBuilder::new()
.add_source("community", &source_repo.bare_file_url(project.sources_path())?)
.add_agent("api-designer-v1", |d| {
d.source("community").path("agents/api-designer.md").version("v0.0.1")
})
.add_agent("api-designer-v2", |d| {
d.source("community").path("agents/api-designer.md").version("v0.0.2")
})
.build();
project.write_manifest(&manifest).await?;
let output = project.run_agpm(&["install"])?;
assert!(
!output.success,
"Install should fail with version conflict. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("Version conflicts detected"),
"Should contain conflict message. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("api-designer.md"),
"Should mention conflicting resource. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("v0.0.1") && output.stderr.contains("v0.0.2"),
"Should mention both conflicting versions. Stderr: {}",
output.stderr
);
Ok(())
}
#[tokio::test]
async fn test_identical_exact_versions_no_conflict() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("test-repo").await.unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent v1.0.0").await.unwrap();
source_repo.commit_all("Initial commit").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let manifest = ManifestBuilder::new()
.add_source("test-repo", &source_repo.bare_file_url(project.sources_path())?)
.add_standard_agent("test-agent-1", "test-repo", "agents/test-agent.md")
.add_standard_agent("test-agent-2", "test-repo", "agents/test-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "Install should succeed. Stderr: {}", output.stderr);
assert!(
!output.stderr.contains("Version conflicts detected"),
"Should not contain conflict message. Stderr: {}",
output.stderr
);
Ok(())
}
#[tokio::test]
async fn test_semver_vs_branch_conflict_blocks_install() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("test-repo").await.unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent v1.0.0").await.unwrap();
source_repo.commit_all("Initial commit").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent v2.0.0").await.unwrap();
source_repo.commit_all("Version 2.0.0").unwrap();
source_repo.tag_version("v2.0.0").unwrap();
source_repo.git.ensure_branch("main").unwrap();
source_repo.git.create_branch("develop").unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent - Development").await.unwrap();
source_repo.commit_all("Development changes").unwrap();
source_repo.git.checkout("main").unwrap();
let manifest = ManifestBuilder::new()
.add_source("test-repo", &source_repo.bare_file_url(project.sources_path())?)
.add_standard_agent("agent-stable", "test-repo", "agents/test-agent.md")
.add_agent("agent-dev", |d| {
d.source("test-repo").path("agents/test-agent.md").branch("main")
})
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
!output.success,
"Install should fail with version conflict. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("Version conflicts detected"),
"Should contain conflict message. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("test-agent.md"),
"Should mention conflicting resource. Stderr: {}",
output.stderr
);
Ok(())
}
#[tokio::test]
async fn test_head_vs_pinned_version_conflict_blocks_install() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("test-repo").await.unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent v1.0.0").await.unwrap();
source_repo.commit_all("Initial commit").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let manifest = ManifestBuilder::new()
.add_source("test-repo", &source_repo.bare_file_url(project.sources_path())?)
.add_agent("agent-head", |d| d.source("test-repo").path("agents/test-agent.md"))
.add_standard_agent("agent-pinned", "test-repo", "agents/test-agent.md")
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
!output.success,
"Install should fail with version conflict. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("Version conflicts detected"),
"Should contain conflict message. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("test-agent.md"),
"Should mention conflicting resource. Stderr: {}",
output.stderr
);
Ok(())
}
#[tokio::test]
async fn test_different_branches_conflict_blocks_install() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("test-repo").await.unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent - Main").await.unwrap();
source_repo.commit_all("Initial commit").unwrap();
source_repo.git.ensure_branch("main").unwrap();
source_repo.git.create_branch("develop").unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent - Development").await.unwrap();
source_repo.commit_all("Development changes").unwrap();
source_repo.git.checkout("main").unwrap();
let manifest = ManifestBuilder::new()
.add_source("test-repo", &source_repo.bare_file_url(project.sources_path())?)
.add_agent("agent-main", |d| {
d.source("test-repo").path("agents/test-agent.md").branch("main")
})
.add_agent("agent-dev", |d| {
d.source("test-repo").path("agents/test-agent.md").branch("develop")
})
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
!output.success,
"Install should fail with version conflict. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("Version conflicts detected"),
"Should contain conflict message. Stderr: {}",
output.stderr
);
assert!(
output.stderr.contains("test-agent.md"),
"Should mention conflicting resource. Stderr: {}",
output.stderr
);
Ok(())
}
#[tokio::test]
async fn test_same_branch_different_case_no_conflict() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("test-repo").await.unwrap();
source_repo.add_resource("agents", "test-agent", "# Test Agent").await.unwrap();
source_repo.commit_all("Initial commit").unwrap();
source_repo.git.ensure_branch("main").unwrap();
if source_repo.git.create_branch("Main").is_ok() {
source_repo.git.checkout("main").unwrap();
}
let manifest = ManifestBuilder::new()
.add_source("test-repo", &source_repo.bare_file_url(project.sources_path())?)
.add_agent("agent-1", |d| d.source("test-repo").path("agents/test-agent.md").branch("main"))
.add_agent("agent-2", |d| d.source("test-repo").path("agents/test-agent.md").branch("Main"))
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "Install should succeed. Stderr: {}", output.stderr);
assert!(
!output.stderr.contains("Version conflicts detected"),
"Should not contain conflict message. Stderr: {}",
output.stderr
);
Ok(())
}
#[tokio::test]
async fn test_changing_dependency_source_no_false_conflict() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("test-repo").await.unwrap();
source_repo.add_resource("commands", "commit", "# Commit Command v1").await.unwrap();
source_repo.commit_all("Initial commit").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let local_dir = project.project_path().join("local-resources");
tokio::fs::create_dir_all(&local_dir.join("commands")).await.unwrap();
tokio::fs::write(local_dir.join("commands/commit.md"), "# Commit Command v2 (local)")
.await
.unwrap();
let manifest = ManifestBuilder::new()
.add_source("test-repo", &source_repo.bare_file_url(project.sources_path())?)
.add_command("commit", |d| {
d.source("test-repo").path("commands/commit.md").version("v1.0.0")
})
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "Initial install should succeed. Stderr: {}", output.stderr);
let local_path = local_dir.to_str().unwrap();
let manifest = ManifestBuilder::new()
.add_command("commit", |d| d.path(&format!("{}/commands/commit.md", local_path)))
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
output.success,
"Install after source change should succeed. Stderr: {}",
output.stderr
);
assert!(
!output.stderr.contains("Target path conflicts detected"),
"Should not contain false conflict error. Stderr: {}",
output.stderr
);
assert!(
!output.stderr.contains("Conflicts: commit, commit"),
"Should not show duplicate names in conflict. Stderr: {}",
output.stderr
);
let lockfile_path = project.project_path().join("agpm.lock");
let lockfile_content = tokio::fs::read_to_string(&lockfile_path).await.unwrap();
let commit_count = lockfile_content.matches("manifest_alias = \"commit\"").count();
assert_eq!(
commit_count, 1,
"Lockfile should have exactly one entry for 'commit', found {}: {}",
commit_count, lockfile_content
);
Ok(())
}
#[tokio::test]
async fn test_pattern_source_change_no_false_conflict() -> Result<()> {
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await.unwrap();
let source_repo1 = project.create_source_repo("repo1").await.unwrap();
source_repo1.add_resource("agents", "helper", "# Helper v1 from repo1").await.unwrap();
source_repo1.add_resource("agents", "worker", "# Worker v1 from repo1").await.unwrap();
source_repo1.commit_all("Initial commit").unwrap();
source_repo1.tag_version("v1.0.0").unwrap();
let source_repo2 = project.create_source_repo("repo2").await.unwrap();
source_repo2.add_resource("agents", "helper", "# Helper v1 from repo2").await.unwrap();
source_repo2.add_resource("agents", "worker", "# Worker v1 from repo2").await.unwrap();
source_repo2.commit_all("Initial commit").unwrap();
source_repo2.tag_version("v1.0.0").unwrap();
let manifest = ManifestBuilder::new()
.add_source("repo1", &source_repo1.bare_file_url(project.sources_path())?)
.add_agent("all-agents", |d| d.source("repo1").path("agents/*.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "Initial pattern install should succeed. Stderr: {}", output.stderr);
let lockfile_content =
tokio::fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
assert!(
lockfile_content.contains("source = \"repo1\""),
"Lockfile should have entries from repo1"
);
let manifest2 = ManifestBuilder::new()
.add_source("repo2", &source_repo2.bare_file_url(project.sources_path())?)
.add_agent("all-agents", |d| d.source("repo2").path("agents/*.md").version("v1.0.0"))
.build();
eprintln!("=== New manifest ===\n{}", manifest2);
project.write_manifest(&manifest2).await.unwrap();
eprintln!("=== Files in repo2 ===");
let repo2_agents_path = source_repo2.path.join("agents");
if repo2_agents_path.exists() {
let entries = std::fs::read_dir(&repo2_agents_path).unwrap();
for entry in entries {
let entry = entry.unwrap();
eprintln!(" - {}", entry.file_name().to_string_lossy());
}
} else {
eprintln!(" agents/ directory does not exist!");
}
let output = project.run_agpm(&["install", "--verbose"]).unwrap();
eprintln!("=== Second install stdout ===\n{}", output.stdout);
eprintln!("=== Second install stderr ===\n{}", output.stderr);
eprintln!("=== Success: {} ===", output.success);
assert!(
output.success,
"Install after pattern source change should succeed. Stderr: {}",
output.stderr
);
assert!(
!output.stderr.contains("Target path conflicts detected"),
"Should not contain false conflict error. Stderr: {}",
output.stderr
);
let updated_lockfile =
tokio::fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
eprintln!("=== Updated lockfile contents ===\n{}", updated_lockfile);
assert!(
updated_lockfile.contains("source = \"repo2\""),
"Lockfile should have been updated to repo2. Lockfile:\n{}",
updated_lockfile
);
assert!(
!updated_lockfile.contains("source = \"repo1\""),
"Lockfile should no longer have repo1 entries"
);
let helper_count = updated_lockfile.matches("name = \"agents/helper\"").count();
let worker_count = updated_lockfile.matches("name = \"agents/worker\"").count();
assert_eq!(
helper_count, 1,
"Lockfile should have exactly one helper entry, found {}: {}",
helper_count, updated_lockfile
);
assert_eq!(
worker_count, 1,
"Lockfile should have exactly one worker entry, found {}: {}",
worker_count, updated_lockfile
);
Ok(())
}
#[tokio::test]
async fn test_source_change_updates_transitive_deps() -> Result<()> {
use crate::common::{ManifestBuilder, TestProject};
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await.unwrap();
let repo1 = project.create_source_repo("repo1").await.unwrap();
let repo2 = project.create_source_repo("repo2").await.unwrap();
let agent_content = r#"---
dependencies:
snippets:
- path: ../snippets/utils.md
---
# Agent A from repo1
"#;
repo1.add_resource("agents", "agent-a", agent_content).await.unwrap();
repo1.add_resource("snippets", "utils", "# Utils from repo1").await.unwrap();
repo1.commit_all("Initial commit").unwrap();
repo1.tag_version("v1.0.0").unwrap();
let agent_content2 = r#"---
dependencies:
snippets:
- path: ../snippets/utils.md
---
# Agent A from repo2
"#;
repo2.add_resource("agents", "agent-a", agent_content2).await.unwrap();
repo2.add_resource("snippets", "utils", "# Utils from repo2").await.unwrap();
repo2.commit_all("Initial commit").unwrap();
repo2.tag_version("v1.0.0").unwrap();
let manifest = ManifestBuilder::new()
.add_source("repo1", &repo1.bare_file_url(project.sources_path())?)
.add_agent("agent-a", |d| d.source("repo1").path("agents/agent-a.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "Initial install should succeed. Stderr: {}", output.stderr);
let lockfile_content =
tokio::fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
eprintln!("=== Initial lockfile ===\n{}", lockfile_content);
assert!(
lockfile_content.contains("manifest_alias = \"agent-a\""),
"Lockfile should contain agent-a"
);
assert!(
lockfile_content.contains("name = \"snippets/utils\"")
&& lockfile_content.matches("source = \"repo1\"").count() >= 2,
"Lockfile should contain transitive dep utils from repo1. Lockfile:\n{}",
lockfile_content
);
let repo1_count = lockfile_content.matches("source = \"repo1\"").count();
assert_eq!(repo1_count, 2, "Lockfile should have 2 entries from repo1 (agent-a + utils)");
let agent_path = project.project_path().join(".claude/agents/agent-a.md");
let utils_path = project.project_path().join(".claude/snippets/utils.md");
assert!(agent_path.exists(), "Agent should be installed");
assert!(utils_path.exists(), "Transitive dep should be installed");
let agent_content_installed = tokio::fs::read_to_string(&agent_path).await.unwrap();
assert!(agent_content_installed.contains("repo1"), "Agent should be from repo1");
let manifest2 = ManifestBuilder::new()
.add_source("repo2", &repo2.bare_file_url(project.sources_path())?)
.add_agent("agent-a", |d| d.source("repo2").path("agents/agent-a.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest2).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
output.success,
"Install after source change should succeed. Stderr: {}",
output.stderr
);
let updated_lockfile =
tokio::fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
assert!(
!updated_lockfile.contains("source = \"repo1\""),
"Lockfile should not have any entries from old repo1. Lockfile:\n{}",
updated_lockfile
);
let repo2_count = updated_lockfile.matches("source = \"repo2\"").count();
assert_eq!(
repo2_count, 2,
"Lockfile should have 2 entries from repo2 (agent-a + utils). Lockfile:\n{}",
updated_lockfile
);
let agent_count = updated_lockfile.matches("name = \"agents/agent-a\"").count();
let utils_count = updated_lockfile.matches("name = \"snippets/utils\"").count();
assert_eq!(
agent_count, 1,
"Lockfile should have exactly one agent-a entry. Lockfile:\n{}",
updated_lockfile
);
assert_eq!(
utils_count, 1,
"Lockfile should have exactly one utils entry. Lockfile:\n{}",
updated_lockfile
);
let agent_content_updated = tokio::fs::read_to_string(&agent_path).await.unwrap();
assert!(agent_content_updated.contains("repo2"), "Agent should now be from repo2");
Ok(())
}
#[tokio::test]
async fn test_pattern_with_transitive_deps_source_change() -> Result<()> {
use crate::common::{ManifestBuilder, TestProject};
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await.unwrap();
let repo1 = project.create_source_repo("repo1").await.unwrap();
let repo2 = project.create_source_repo("repo2").await.unwrap();
let helper_content = r#"---
dependencies:
snippets:
- path: ../snippets/utils.md
---
# Helper from repo1
"#;
let worker_content = r#"---
dependencies:
snippets:
- path: ../snippets/utils.md
---
# Worker from repo1
"#;
repo1.add_resource("agents", "helper", helper_content).await.unwrap();
repo1.add_resource("agents", "worker", worker_content).await.unwrap();
repo1.add_resource("snippets", "utils", "# Utils from repo1").await.unwrap();
repo1.commit_all("Initial commit").unwrap();
repo1.tag_version("v1.0.0").unwrap();
let helper_content2 = r#"---
dependencies:
snippets:
- path: ../snippets/utils.md
---
# Helper from repo2
"#;
let worker_content2 = r#"---
dependencies:
snippets:
- path: ../snippets/utils.md
---
# Worker from repo2
"#;
repo2.add_resource("agents", "helper", helper_content2).await.unwrap();
repo2.add_resource("agents", "worker", worker_content2).await.unwrap();
repo2.add_resource("snippets", "utils", "# Utils from repo2").await.unwrap();
repo2.commit_all("Initial commit").unwrap();
repo2.tag_version("v1.0.0").unwrap();
let manifest = ManifestBuilder::new()
.add_source("repo1", &repo1.bare_file_url(project.sources_path())?)
.add_agent("all-agents", |d| d.source("repo1").path("agents/*.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "Initial pattern install should succeed. Stderr: {}", output.stderr);
let lockfile_content =
tokio::fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
eprintln!("=== Initial lockfile ===\n{}", lockfile_content);
assert!(lockfile_content.contains("name = \"agents/helper\""), "Should have helper");
assert!(lockfile_content.contains("name = \"agents/worker\""), "Should have worker");
assert!(
lockfile_content.contains("name = \"snippets/utils\"")
&& lockfile_content.matches("source = \"repo1\"").count() >= 3,
"Should have transitive utils from repo1. Lockfile:\n{}",
lockfile_content
);
let repo1_count = lockfile_content.matches("source = \"repo1\"").count();
assert_eq!(
repo1_count, 3,
"Lockfile should have 3 entries from repo1 (helper + worker + utils). Found: {}",
repo1_count
);
let helper_path = project.project_path().join(".claude/agents/helper.md");
let worker_path = project.project_path().join(".claude/agents/worker.md");
let utils_path = project.project_path().join(".claude/snippets/utils.md");
assert!(helper_path.exists(), "Helper should be installed");
assert!(worker_path.exists(), "Worker should be installed");
assert!(utils_path.exists(), "Utils should be installed");
let manifest2 = ManifestBuilder::new()
.add_source("repo2", &repo2.bare_file_url(project.sources_path())?)
.add_agent("all-agents", |d| d.source("repo2").path("agents/*.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest2).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(
output.success,
"Install after pattern source change should succeed. Stderr: {}",
output.stderr
);
let updated_lockfile =
tokio::fs::read_to_string(project.project_path().join("agpm.lock")).await.unwrap();
eprintln!("=== Updated lockfile ===\n{}", updated_lockfile);
assert!(
!updated_lockfile.contains("source = \"repo1\""),
"Lockfile should not have any entries from old repo1. Lockfile:\n{}",
updated_lockfile
);
let repo2_count = updated_lockfile.matches("source = \"repo2\"").count();
assert_eq!(
repo2_count, 3,
"Lockfile should have 3 entries from repo2 (helper + worker + utils). Lockfile:\n{}",
updated_lockfile
);
let helper_count = updated_lockfile.matches("name = \"agents/helper\"").count();
let worker_count = updated_lockfile.matches("name = \"agents/worker\"").count();
let utils_count = updated_lockfile.matches("name = \"snippets/utils\"").count();
assert_eq!(
helper_count, 1,
"Lockfile should have exactly one helper entry. Lockfile:\n{}",
updated_lockfile
);
assert_eq!(
worker_count, 1,
"Lockfile should have exactly one worker entry. Lockfile:\n{}",
updated_lockfile
);
assert_eq!(
utils_count, 1,
"Lockfile should have exactly one utils entry. Lockfile:\n{}",
updated_lockfile
);
let helper_content = tokio::fs::read_to_string(&helper_path).await.unwrap();
let worker_content = tokio::fs::read_to_string(&worker_path).await.unwrap();
let utils_content = tokio::fs::read_to_string(&utils_path).await.unwrap();
assert!(helper_content.contains("repo2"), "Helper should be from repo2");
assert!(worker_content.contains("repo2"), "Worker should be from repo2");
assert!(utils_content.contains("repo2"), "Utils should be from repo2");
Ok(())
}
#[tokio::test]
async fn test_commented_out_dependency_removed_from_lockfile() -> Result<()> {
let project = TestProject::new().await.unwrap();
let source_repo = project.create_source_repo("source").await.unwrap();
source_repo.add_resource("agents", "agent-a", "# Agent A\nFirst agent").await.unwrap();
source_repo.add_resource("agents", "agent-b", "# Agent B\nSecond agent").await.unwrap();
source_repo.commit_all("Initial agents").unwrap();
source_repo.tag_version("v1.0.0").unwrap();
let source_url = source_repo.bare_file_url(project.sources_path())?;
let manifest = ManifestBuilder::new()
.add_source("source", &source_url)
.add_agent("agent-a", |d| d.source("source").path("agents/agent-a.md").version("v1.0.0"))
.add_agent("agent-b", |d| d.source("source").path("agents/agent-b.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest).await.unwrap();
let output = project.run_agpm(&["install"]).unwrap();
assert!(output.success, "First install should succeed. stderr: {}", output.stderr);
let lockfile_content = project.read_lockfile().await.unwrap();
assert!(
lockfile_content.contains("manifest_alias = \"agent-a\""),
"Lockfile should contain agent-a"
);
assert!(
lockfile_content.contains("manifest_alias = \"agent-b\""),
"Lockfile should contain agent-b"
);
let manifest2 = ManifestBuilder::new()
.add_source("source", &source_url)
.add_agent("agent-a", |d| d.source("source").path("agents/agent-a.md").version("v1.0.0"))
.build();
project.write_manifest(&manifest2).await.unwrap();
let output2 = project.run_agpm(&["install"]).unwrap();
assert!(
output2.success,
"Second install should succeed without conflicts. stderr: {}",
output2.stderr
);
let updated_lockfile = project.read_lockfile().await.unwrap();
assert!(
updated_lockfile.contains("manifest_alias = \"agent-a\""),
"Lockfile should still contain agent-a"
);
assert!(
!updated_lockfile.contains("manifest_alias = \"agent-b\""),
"Lockfile should NOT contain agent-b after commenting out. Lockfile:\n{}",
updated_lockfile
);
assert!(
!output2.stderr.contains("conflict"),
"Should not have any conflicts. stderr: {}",
output2.stderr
);
let agent_a_path = project.project_path().join(".claude/agents/agent-a.md");
assert!(agent_a_path.exists(), "Agent A file should exist");
Ok(())
}
#[tokio::test]
async fn test_local_direct_and_transitive_deps_no_false_conflict() -> Result<()> {
agpm_cli::test_utils::init_test_logging(None);
let project = TestProject::new().await?;
let local_dir = project.project_path().join("local-agents");
tokio::fs::create_dir_all(&local_dir.join("agents")).await?;
let _local_path = local_dir.to_str().unwrap();
let helper_content = "# Helper Agent\nProvides helper functionality";
tokio::fs::write(local_dir.join("agents/helper.md"), helper_content).await?;
let parent_content = r#"---
dependencies:
agents:
- path: helper.md
---
# Parent Agent
Uses the helper agent"#;
tokio::fs::write(local_dir.join("agents/parent.md"), parent_content).await?;
let manifest = ManifestBuilder::new()
.add_agent("my-helper", |d| d.path("local-agents/agents/helper.md"))
.add_agent("parent", |d| d.path("local-agents/agents/parent.md"))
.build();
project.write_manifest(&manifest).await?;
let output = project.run_agpm(&["install"])?;
assert!(
output.success,
"Install should succeed without false conflicts. Stderr: {}",
output.stderr
);
assert!(
!output.stderr.contains("Target path conflicts detected"),
"Should not report false conflicts. Stderr: {}",
output.stderr
);
assert!(
!output.stderr.contains("Version conflicts detected"),
"Should not report version conflicts. Stderr: {}",
output.stderr
);
let lockfile = project.read_lockfile().await?;
let helper_entries = lockfile.matches("path = \"local-agents/agents/helper.md\"").count();
assert_eq!(
helper_entries, 1,
"Lockfile should have exactly one entry for helper.md (deduplicated by path). Found {}: {}",
helper_entries, lockfile
);
let parent_path = project.project_path().join(".claude/agents/parent.md");
assert!(parent_path.exists(), "Parent should be installed");
assert!(
lockfile.contains("manifest_alias = \"my-helper\""),
"Lockfile should use the direct dependency name 'my-helper'. Lockfile: {}",
lockfile
);
assert!(
!lockfile.contains("name = \"helper\"")
|| lockfile.matches("name = \"helper\"").count() == 0,
"Lockfile should not have a separate 'helper' entry (should be deduplicated). Lockfile: {}",
lockfile
);
let helper_path = project.project_path().join(".claude/agents/helper.md");
assert!(helper_path.exists(), "Helper file should be installed");
Ok(())
}