Module resolver

Module resolver 

Source
Expand description

Dependency resolution and conflict detection for AGPM.

This module implements the core dependency resolution algorithm that transforms manifest dependencies into locked versions. It handles version constraint solving, conflict detection, transitive dependency resolution, parallel source synchronization, and relative path preservation during installation.

§Architecture Overview

The resolver operates using a two-phase architecture optimized for SHA-based worktree caching:

§Phase 1: Source Synchronization (pre_sync_sources)

  • Purpose: Perform all Git network operations upfront during “Syncing sources” phase
  • Operations: Clone/fetch repositories, update refs, populate cache
  • Benefits: Clear progress reporting, batch network operations, error isolation
  • Result: All required repositories cached locally for phase 2

§Phase 2: Version Resolution (resolve or update)

  • Purpose: Resolve versions to commit SHAs using cached repositories
  • Operations: Parse dependencies, resolve constraints, detect conflicts, create worktrees, preserve paths
  • Benefits: Fast local operations, no network I/O, deterministic behavior
  • Result: Locked dependencies ready for installation with preserved directory structure

This two-phase approach replaces the previous three-phase model and provides better separation of concerns between network operations and dependency resolution logic.

§Algorithm Complexity

  • Time: O(n + s·log(t)) where:
    • n = number of dependencies
    • s = number of unique sources
    • t = average number of tags/branches per source
  • Space: O(n + s) for dependency graph and source cache

§Parallel Processing

The resolver leverages async/await for concurrent operations:

  • Sources are synchronized in parallel using tokio::spawn
  • Git operations are batched to minimize network roundtrips
  • Progress reporting provides real-time feedback on long-running operations

§Resolution Process

The two-phase dependency resolution follows these steps:

§Phase 1: Source Synchronization

  1. Dependency Collection: Extract all dependencies from manifest
  2. Source Validation: Verify all referenced sources exist and are accessible
  3. Repository Preparation: Use version_resolver::VersionResolver to collect unique sources
  4. Source Synchronization: Clone/update source repositories with single fetch per repository
  5. Cache Population: Store bare repository paths for phase 2 operations

§Phase 2: Version Resolution & Installation

  1. Batch SHA Resolution: Resolve all collected versions to commit SHAs using cached repositories
  2. SHA-based Worktree Creation: Create worktrees keyed by commit SHA for maximum deduplication
  3. Conflict Detection: Check for path conflicts and incompatible versions
  4. Redundancy Analysis: Identify duplicate resources across sources
  5. Path Processing: Preserve directory structure by using paths directly from dependencies
  6. Resource Installation: Copy resources to target locations with checksums
  7. Lockfile Generation: Create deterministic lockfile entries with resolved SHAs and preserved paths

This separation ensures all network operations complete in phase 1, while phase 2 operates entirely on cached data for fast, deterministic resolution.

§Version Resolution Strategy

Version constraints are resolved using the following precedence:

  1. Exact commits: SHA hashes are used directly
  2. Tags: Semantic version tags (e.g., v1.2.3) are preferred
  3. Branches: Branch heads are resolved to current commits
  4. Latest: Defaults to the default branch (usually main or master)

§Conflict Detection

The resolver detects several types of conflicts:

§Version Conflicts

# Incompatible version constraints for the same resource
[agents]
app = { source = "community", path = "agents/helper.md", version = "v1.0.0" }
tool = { source = "community", path = "agents/helper.md", version = "v2.0.0" }

§Path Conflicts

# Different resources installing to the same location
[agents]
helper-v1 = { source = "old", path = "agents/helper.md" }
helper-v2 = { source = "new", path = "agents/helper.md" }

§Source Conflicts

When the same resource path exists in multiple sources with different content, the resolver uses source precedence (global config sources override local manifest sources).

§Security Considerations

The resolver implements several security measures:

  • Input Validation: All Git references are validated before checkout
  • Path Sanitization: Installation paths are validated to prevent directory traversal
  • Credential Isolation: Authentication tokens are never stored in manifest files
  • Checksum Verification: Resources are checksummed for integrity validation

§Performance Optimizations

  • SHA-based Worktree Caching: Worktrees keyed by commit SHA maximize reuse across versions
  • Batch Version Resolution: All versions resolved to SHAs upfront via version_resolver::VersionResolver
  • Single Fetch Per Repository: Command-instance fetch caching eliminates redundant network operations
  • Source Caching: Git repositories are cached globally in ~/.agpm/cache/
  • Incremental Updates: Only modified sources are re-synchronized
  • Parallel Operations: Source syncing and version resolution run concurrently
  • Progress Batching: UI updates are throttled to prevent performance impact

§Error Handling

The resolver provides detailed error context for common failure scenarios:

  • Network Issues: Graceful handling of Git clone/fetch failures
  • Authentication: Clear error messages for credential problems
  • Version Mismatches: Specific guidance for constraint resolution failures
  • Path Issues: Detailed information about file system conflicts

§Example Usage

§Two-Phase Resolution Pattern

use agpm_cli::resolver::DependencyResolver;
use agpm_cli::manifest::Manifest;
use agpm_cli::cache::Cache;
use std::path::Path;

let manifest = Manifest::load(Path::new("agpm.toml"))?;
let cache = Cache::new()?;
let mut resolver = DependencyResolver::new_with_global(manifest.clone(), cache).await?;

// Get all dependencies from manifest
let deps: Vec<(String, agpm_cli::manifest::ResourceDependency)> = manifest
    .all_dependencies()
    .into_iter()
    .map(|(name, dep)| (name.to_string(), dep.clone()))
    .collect();

// Phase 1: Sync all required sources (network operations)
resolver.pre_sync_sources(&deps).await?;

// Phase 2: Resolve dependencies using cached repositories (local operations)
let lockfile = resolver.resolve().await?;

println!("Resolved {} agents and {} snippets",
         lockfile.agents.len(), lockfile.snippets.len());

§Update Pattern

let manifest = Manifest::load(Path::new("agpm.toml"))?;
let mut lockfile = LockFile::load(Path::new("agpm.lock"))?;
let cache = Cache::new()?;
let mut resolver = DependencyResolver::with_cache(manifest.clone(), cache);

// Get dependencies to update
let deps: Vec<(String, agpm_cli::manifest::ResourceDependency)> = manifest
    .all_dependencies()
    .into_iter()
    .map(|(name, dep)| (name.to_string(), dep.clone()))
    .collect();

// Phase 1: Sync sources for update
resolver.pre_sync_sources(&deps).await?;

// Phase 2: Update specific dependencies
resolver.update(&mut lockfile, None).await?;

lockfile.save(Path::new("agpm.lock"))?;

§Incremental Updates

use agpm_cli::resolver::DependencyResolver;
use agpm_cli::lockfile::LockFile;
use agpm_cli::cache::Cache;
use std::path::Path;

let existing = LockFile::load("agpm.lock".as_ref())?;
let manifest = agpm_cli::manifest::Manifest::load("agpm.toml".as_ref())?;
let cache = Cache::new()?;
let mut resolver = DependencyResolver::new(manifest, cache)?;

// Update specific dependencies only
let deps_to_update = vec!["agent1".to_string(), "snippet2".to_string()];
let deps_count = deps_to_update.len();
let updated = resolver.update(&existing, Some(deps_to_update)).await?;

println!("Updated {} dependencies", deps_count);

Modules§

dependency_graph
Dependency graph management for transitive dependency resolution.
version_resolution
Version constraint resolution helpers for the dependency resolver.
version_resolver
Centralized version resolution module for AGPM

Structs§

DependencyResolver
Core dependency resolver that transforms manifest dependencies into lockfile entries.

Functions§

extract_meaningful_path
Extract meaningful path structure from a dependency path.