LockFile

Struct LockFile 

Source
pub struct LockFile {
    pub version: u32,
    pub sources: Vec<LockedSource>,
    pub agents: Vec<LockedResource>,
    pub snippets: Vec<LockedResource>,
    pub commands: Vec<LockedResource>,
    pub mcp_servers: Vec<LockedResource>,
    pub scripts: Vec<LockedResource>,
    pub hooks: Vec<LockedResource>,
    pub skills: Vec<LockedResource>,
    pub manifest_hash: Option<String>,
    pub has_mutable_deps: Option<bool>,
    pub resource_count: Option<usize>,
}
Expand description

The main lockfile structure representing a complete agpm.lock file.

This structure contains all resolved dependencies, source repositories, and their exact versions/commits for reproducible installations. The lockfile is automatically generated from the crate::manifest::Manifest during installation and should not be edited manually.

§Format Version

The lockfile includes a format version to enable future migrations and compatibility checking. The current version is 1.

§Serialization

The lockfile serializes to TOML format with arrays of sources, agents, and snippets. Empty arrays are omitted from serialization to keep the lockfile clean.

§Examples

Creating a new lockfile:

use agpm_cli::lockfile::LockFile;

let lockfile = LockFile::new();
assert_eq!(lockfile.version, 1);
assert!(lockfile.sources.is_empty());

Loading an existing lockfile:

let lockfile = LockFile::load(Path::new("agpm.lock"))?;
println!("Loaded {} sources, {} agents",
         lockfile.sources.len(), lockfile.agents.len());

Fields§

§version: u32

Version of the lockfile format.

This field enables forward and backward compatibility checking. AGPM will refuse to load lockfiles with versions newer than it supports, and may provide migration paths for older versions in the future.

§sources: Vec<LockedSource>

Locked source repositories with their resolved commit hashes.

Each entry represents a Git repository that has been fetched and resolved to an exact commit. The commit hash ensures all team members get identical source content even as the upstream repository evolves.

This field is omitted from TOML serialization if empty to keep the lockfile clean.

§agents: Vec<LockedResource>

Locked agent resources with their exact versions and checksums.

Contains all resolved agent dependencies from the manifest, with exact commit hashes, installation paths, and SHA-256 checksums for integrity verification.

This field is omitted from TOML serialization if empty.

§snippets: Vec<LockedResource>

Locked snippet resources with their exact versions and checksums.

Contains all resolved snippet dependencies from the manifest, with exact commit hashes, installation paths, and SHA-256 checksums for integrity verification.

This field is omitted from TOML serialization if empty.

§commands: Vec<LockedResource>

Locked command resources with their exact versions and checksums.

Contains all resolved command dependencies from the manifest, with exact commit hashes, installation paths, and SHA-256 checksums for integrity verification.

This field is omitted from TOML serialization if empty.

§mcp_servers: Vec<LockedResource>

Locked MCP server resources with their exact versions and checksums.

Contains all resolved MCP server dependencies from the manifest, with exact commit hashes, installation paths, and SHA-256 checksums for integrity verification. MCP servers are installed as JSON files and also configured in .claude/settings.local.json.

This field is omitted from TOML serialization if empty.

§scripts: Vec<LockedResource>

Locked script resources with their exact versions and checksums.

Contains all resolved script dependencies from the manifest, with exact commit hashes, installation paths, and SHA-256 checksums for integrity verification. Scripts are executable files that can be referenced by hooks.

This field is omitted from TOML serialization if empty.

§hooks: Vec<LockedResource>

Locked hook configurations with their exact versions and checksums.

Contains all resolved hook dependencies from the manifest. Hooks are JSON configuration files that define event-based automation in Claude Code.

This field is omitted from TOML serialization if empty.

§skills: Vec<LockedResource>

Locked skill resources with their exact versions and checksums.

Skills are directory-based resources (unlike single-file agents/snippets) that contain a SKILL.md file plus supporting files. Contains all resolved skill dependencies with exact commit hashes, installation paths, and checksums.

This field is omitted from TOML serialization if empty.

§manifest_hash: Option<String>

Hash of manifest dependency specifications for fast path detection.

This field stores a SHA-256 hash of the manifest’s dependency sections (sources, agents, commands, etc.) at the time of resolution. On subsequent installs, if this hash matches the current manifest, we can skip resolution entirely for immutable dependencies.

This field is omitted from TOML serialization if None (for backward compatibility).

§has_mutable_deps: Option<bool>

Whether the lockfile contains any mutable dependencies.

Mutable dependencies include:

  • Local file sources (no URL)
  • Branch-based versions (e.g., “main”, “develop”)

When true, even if the manifest hash matches, we must re-validate mutable dependencies as they may have changed. When false, we can use the full fast path and skip resolution entirely.

This field is omitted from TOML serialization if None (for backward compatibility).

§resource_count: Option<usize>

Total count of resources in the lockfile at time of resolution.

This field is used for lockfile integrity validation during fast path checks. If the stored count doesn’t match the actual resource count in the lockfile, it indicates the lockfile was manually edited and we should fall back to full resolution.

This field is omitted from TOML serialization if None (for backward compatibility).

Implementations§

Source§

impl LockFile

Source

pub fn compute_checksum(path: &Path) -> Result<String>

Compute SHA-256 checksum for file integrity verification.

Detects corruption, tampering, or changes after installation.

§Arguments
  • path - Path to the file to checksum
§Returns
  • Ok(String) - Checksum in format “sha256:hexadecimal_hash
  • Err(anyhow::Error) - File read error with detailed context
§Checksum Format

The returned checksum follows the format:

  • Algorithm prefix: “sha256:”
  • Hash encoding: Lowercase hexadecimal
  • Length: 71 characters total (7 for prefix + 64 hex digits)
§Examples
use std::path::Path;
use agpm_cli::lockfile::LockFile;

let checksum = LockFile::compute_checksum(Path::new("example.md"))?;
println!("File checksum: {}", checksum);
// Output: "sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3"
§Error Handling

Provides detailed error context for common issues:

  • File not found: Suggests checking the path
  • Permission denied: Suggests checking file permissions
  • IO errors: Suggests checking disk health or file locks
§Security Considerations
  • Uses SHA-256, a cryptographically secure hash function
  • Suitable for integrity verification and tamper detection
  • Consistent across platforms (Windows, macOS, Linux)
  • Not affected by line ending differences (hashes actual bytes)
§Performance

The method reads the entire file into memory before hashing. For very large files (>100MB), consider streaming implementations in future versions.

Source

pub fn compute_directory_checksum(path: &Path) -> Result<String>

Compute SHA-256 checksum for a directory (skill resources).

Calculates a combined checksum of all files in a directory by concatenating their individual checksums in sorted order. This provides a deterministic checksum that changes when any file in the directory changes.

§Arguments
  • path - Path to the directory to checksum
§Returns
  • Ok(String) - Combined checksum in format “sha256:hexadecimal_hash
  • Err(anyhow::Error) - Directory read or file hash error
§Algorithm
  1. Walk directory recursively (files only, not directories)
  2. Compute SHA-256 of each file
  3. Sort file paths for deterministic ordering
  4. Concatenate all checksums with file paths
  5. Compute final SHA-256 of the concatenated data
§Examples
use std::path::Path;
use agpm_cli::lockfile::LockFile;

let checksum = LockFile::compute_directory_checksum(Path::new("my-skill"))?;
println!("Directory checksum: {}", checksum);
Source

pub fn verify_checksum(path: &Path, expected: &str) -> Result<bool>

Verify file matches expected checksum.

Computes current checksum and compares with expected value.

§Arguments
  • path - Path to the file to verify
  • expected - Expected checksum in “sha256:hex” format
§Returns
  • Ok(true) - File checksum matches expected value
  • Ok(false) - File checksum does not match (corruption detected)
  • Err(anyhow::Error) - File read error or checksum calculation failed
§Examples
use std::path::Path;
use agpm_cli::lockfile::LockFile;

let expected = "sha256:a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3";
let is_valid = LockFile::verify_checksum(Path::new("example.md"), expected)?;

if is_valid {
    println!("File integrity verified");
} else {
    println!("WARNING: File has been modified or corrupted!");
}
§Use Cases
  • Installation verification: Ensure copied files are intact
  • Periodic validation: Detect file corruption over time
  • Security checks: Detect unauthorized modifications
  • Troubleshooting: Diagnose installation issues
§Performance

This method internally calls compute_checksum, so it has the same performance characteristics. For bulk verification operations, consider caching computed checksums.

§Security

The comparison is performed using standard string equality, which is not timing-attack resistant. Since checksums are not secrets, this is acceptable for integrity verification purposes.

Source

pub fn update_resource_checksum( &mut self, id: &ResourceId, checksum: &str, ) -> bool

Update checksum for resource identified by ResourceId.

Used after installation to record actual file checksum. ResourceId ensures unique identification via name, source, tool, and template_vars.

§Arguments
  • id - The unique identifier for the resource
  • checksum - The new SHA-256 checksum in “sha256:hex” format
§Returns

Returns true if the resource was found and updated, false otherwise.

§Examples
let variant_hash = compute_variant_inputs_hash(&serde_json::json!({})).unwrap_or_default();
let id = ResourceId::new("my-agent", None::<String>, Some("claude-code"), ResourceType::Agent, variant_hash);
let updated = lockfile.update_resource_checksum(&id, "sha256:abcdef123456...");
assert!(updated);
Source

pub fn update_resource_context_checksum( &mut self, id: &ResourceId, context_checksum: &str, ) -> bool

Update context checksum for resource by ResourceId.

Stores the SHA-256 checksum of template rendering inputs (context) in the lockfile. This is different from the file checksum which covers the final rendered content.

§Arguments
  • id - The ResourceId identifying the resource to update
  • context_checksum - The SHA-256 checksum of template context, or None for non-templated resources
§Returns

Returns true if the resource was found and updated, false otherwise.

§Examples
let mut lockfile = LockFile::new();
let id = ResourceId::new("my-agent", None::<String>, Some("claude-code"), ResourceType::Agent, serde_json::json!({}));
let updated = lockfile.update_resource_context_checksum(&id, Some("sha256:context123456..."));
assert!(updated);
Source

pub fn update_resource_applied_patches( &mut self, name: &str, applied_patches: &AppliedPatches, ) -> bool

Update applied patches for resource by name.

Stores project patches in main lockfile; private patches go to agpm.private.lock. Takes AppliedPatches from installer.

§Arguments
  • name - The name of the resource to update
  • applied_patches - The patches that were applied (from AppliedPatches struct)
§Returns

Returns true if the resource was found and updated, false otherwise.

§Examples
let mut applied = AppliedPatches::new();
applied.project.insert("model".to_string(), toml::Value::String("haiku".into()));

let updated = lockfile.update_resource_applied_patches("my-agent", &applied);
assert!(updated);
Source

pub fn apply_installation_results( &mut self, checksums: Vec<(ResourceId, String)>, context_checksums: Vec<(ResourceId, Option<String>)>, applied_patches_list: Vec<(ResourceId, AppliedPatches)>, token_counts: Vec<(ResourceId, Option<u64>)>, )

Apply installation results to the lockfile in batch.

Updates the lockfile with checksums, context checksums, and applied patches from the installation process. This consolidates three separate update operations into one batch call, reducing code duplication between install and update commands.

§Batch Processing Pattern

This function processes three parallel vectors of installation results:

  1. File checksums - SHA-256 of rendered content (triggers reinstall if changed)
  2. Context checksums - SHA-256 of template inputs (audit/debug only)
  3. Applied patches - Tracks which project patches were applied to each resource

The batch approach ensures all three updates are applied consistently and atomically to the lockfile, avoiding partial state.

§Arguments
  • checksums - File checksums for each installed resource (by ResourceId)
  • context_checksums - Context checksums for template inputs (Optional)
  • applied_patches_list - Patches that were applied to each resource
§Implementation Details
  • Updates are applied by ResourceId to handle duplicate resource names correctly
  • Context checksums are only applied if present (non-templated resources have None)
  • Only project patches are stored; private patches go to agpm.private.lock
  • Called by both install and update commands after parallel installation
§Examples
let mut lockfile = LockFile::default();

// Collect results from parallel installation
let checksums = vec![/* (ResourceId, checksum) pairs */];
let context_checksums = vec![/* (ResourceId, Option<checksum>) pairs */];
let applied_patches = vec![/* (ResourceId, AppliedPatches) pairs */];
let token_counts = vec![/* (ResourceId, Option<u64>) pairs */];

// Apply all results in batch (replaces 3 separate loops)
lockfile.apply_installation_results(
    checksums,
    context_checksums,
    applied_patches,
    token_counts,
);
Source§

impl LockFile

Source

pub fn load(path: &Path) -> Result<Self>

Load lockfile from disk with error handling and validation.

Returns empty lockfile if file doesn’t exist. Performs format version compatibility checking with detailed error messages.

§Arguments
  • path - Path to the lockfile (typically “agpm.lock”)
§Returns
  • Ok(LockFile) - Successfully loaded lockfile or new empty lockfile if file doesn’t exist
  • Err(anyhow::Error) - Parse error, IO error, or version incompatibility
§Error Handling

This method provides detailed error messages for common issues:

  • File not found: Returns empty lockfile (not an error)
  • Permission denied: Suggests checking file ownership/permissions
  • TOML parse errors: Suggests regenerating lockfile or checking syntax
  • Version incompatibility: Suggests updating AGPM
  • Empty file: Returns empty lockfile (graceful handling)
§Examples
use std::path::Path;
use agpm_cli::lockfile::LockFile;

// Load existing lockfile
let lockfile = LockFile::load(Path::new("agpm.lock"))?;
println!("Loaded {} sources", lockfile.sources.len());

// Non-existent file returns empty lockfile
let empty = LockFile::load(Path::new("missing.lock"))?;
assert!(empty.sources.is_empty());
§Version Compatibility

The method checks the lockfile format version and will refuse to load lockfiles created by newer versions of AGPM:

Error: Lockfile version 2 is newer than supported version 1.
This lockfile was created by a newer version of agpm.
Please update agpm to the latest version to use this lockfile.
Source

pub fn save(&self, path: &Path) -> Result<()>

Save lockfile to disk with atomic writes and custom formatting.

Serializes to TOML with header warning and custom formatting. Uses atomic writes (temp file + rename) to prevent corruption.

§Arguments
  • path - Path where to save the lockfile (typically “agpm.lock”)
§Returns
  • Ok(()) - Successfully saved lockfile
  • Err(anyhow::Error) - IO error, permission denied, or disk full
§Atomic Write Behavior

The save operation is atomic - the lockfile is written to a temporary file and then renamed to the target path. This ensures the lockfile is never left in a partially written state even if the process is interrupted.

§Custom Formatting

The method uses custom TOML formatting instead of standard serde serialization to produce more readable output:

  • Adds header comment warning against manual editing
  • Groups related fields together
  • Uses consistent indentation and spacing
  • Omits empty arrays to keep the file clean
§Error Handling

Provides detailed error messages for common issues:

  • Permission denied: Suggests running with elevated permissions
  • Directory doesn’t exist: Suggests creating parent directories
  • Disk full: Suggests freeing space or using different location
  • File locked: Suggests closing other programs using the file
§Examples
use std::path::Path;
use agpm_cli::lockfile::LockFile;

let mut lockfile = LockFile::new();

// Add a source
lockfile.add_source(
    "community".to_string(),
    "https://github.com/example/repo.git".to_string(),
    "a1b2c3d4e5f6...".to_string()
);

// Save to disk
lockfile.save(Path::new("agpm.lock"))?;
§Generated File Format

The saved file starts with a warning header:

# Auto-generated lockfile - DO NOT EDIT
version = 1

[[sources]]
name = "community"
url = "https://github.com/example/repo.git"
commit = "a1b2c3d4e5f6..."
fetched_at = "2024-01-15T10:30:00Z"
Source§

impl LockFile

Source

pub fn add_source(&mut self, name: String, url: String, _commit: String)

Add or update source repository, setting fetched_at to current UTC time.

Replaces existing source with same name.

§Arguments
  • name - Unique source identifier (matches manifest [sources] keys)
  • url - Full Git repository URL
  • commit - Resolved 40-character commit hash
§Behavior

If a source with the same name already exists, it will be replaced with the new information. This ensures that each source name appears exactly once in the lockfile.

§Examples
use agpm_cli::lockfile::LockFile;

let mut lockfile = LockFile::new();
lockfile.add_source(
    "community".to_string(),
    "https://github.com/example/community.git".to_string(),
    "a1b2c3d4e5f6789abcdef0123456789abcdef012".to_string()
);

assert_eq!(lockfile.sources.len(), 1);
assert_eq!(lockfile.sources[0].name, "community");
§Time Zone

The fetched_at timestamp is always recorded in UTC to ensure consistency across different time zones and systems.

Source

pub fn get_source(&self, name: &str) -> Option<&LockedSource>

Find source repository by name.

§Arguments
  • name - Source name to search for (matches manifest [sources] keys)
§Returns
  • Some(&LockedSource) - Reference to the found source
  • None - No source with that name exists
§Examples
if let Some(source) = lockfile.get_source("community") {
    println!("Source URL: {}", source.url);
    println!("Fetched at: {}", source.fetched_at);
}
Source

pub fn add_resource( &mut self, name: String, resource: LockedResource, is_agent: bool, )

Add or update resource (agents or snippets only).

Replaces existing resource with same name.

Note: Backward compatibility only. Use add_typed_resource for all types.

§Arguments
  • name - Unique resource identifier within its type
  • resource - Complete LockedResource with all resolved information
  • is_agent - true for agents, false for snippets
§Behavior

If a resource with the same name already exists in the same type category, it will be replaced. Resources are categorized separately (agents vs snippets), so an agent named “helper” and a snippet named “helper” can coexist.

§Examples

Adding an agent:

use agpm_cli::lockfile::{LockFile, LockedResourceBuilder};
use agpm_cli::core::ResourceType;

let mut lockfile = LockFile::new();
let resource = LockedResourceBuilder::new(
    "example-agent".to_string(),
    "agents/example.md".to_string(),
    "sha256:abcdef...".to_string(),
    "agents/example-agent.md".to_string(),
    ResourceType::Agent,
)
.source(Some("community".to_string()))
.url(Some("https://github.com/example/repo.git".to_string()))
.version(Some("^1.0".to_string()))
.resolved_commit(Some("a1b2c3d...".to_string()))
.tool(Some("claude-code".to_string()))
.dependencies(Vec::new())
.applied_patches(std::collections::BTreeMap::new())
.build();

lockfile.add_resource("example-agent".to_string(), resource, true);
assert_eq!(lockfile.agents.len(), 1);

Adding a snippet:

let snippet = LockedResourceBuilder::new(
    "util-snippet".to_string(),
    "../local/utils.md".to_string(),
    "sha256:fedcba...".to_string(),
    "snippets/util-snippet.md".to_string(),
    ResourceType::Snippet,
)
.tool(Some("claude-code".to_string()))
.dependencies(Vec::new())
.applied_patches(std::collections::BTreeMap::new())
.build();

lockfile.add_resource("util-snippet".to_string(), snippet, false);
assert_eq!(lockfile.snippets.len(), 1);
Source

pub fn add_typed_resource( &mut self, name: String, resource: LockedResource, resource_type: ResourceType, )

Add or update resource with explicit type support.

Preferred method - supports all resource types.

§Arguments
  • name - Unique resource identifier within its type
  • resource - Complete LockedResource with all resolved information
  • resource_type - The type of resource (Agent, Snippet, or Command)
§Examples
use agpm_cli::lockfile::{LockFile, LockedResourceBuilder};
use agpm_cli::core::ResourceType;

let mut lockfile = LockFile::new();
let command = LockedResourceBuilder::new(
    "build-command".to_string(),
    "commands/build.md".to_string(),
    "sha256:abcdef...".to_string(),
    ".claude/commands/build-command.md".to_string(),
    ResourceType::Command,
)
.source(Some("community".to_string()))
.url(Some("https://github.com/example/repo.git".to_string()))
.version(Some("v1.0.0".to_string()))
.resolved_commit(Some("a1b2c3d...".to_string()))
.tool(Some("claude-code".to_string()))
.dependencies(Vec::new())
.applied_patches(std::collections::BTreeMap::new())
.build();

lockfile.add_typed_resource("build-command".to_string(), command, ResourceType::Command);
assert_eq!(lockfile.commands.len(), 1);
Source

pub fn has_resource(&self, name: &str) -> bool

Check if resource exists by name.

§Arguments
  • name - Resource name to check
§Returns
  • true - Resource exists in the lockfile
  • false - Resource does not exist
§Examples
if lockfile.has_resource("example-agent") {
    println!("Agent is already locked");
} else {
    println!("Agent needs to be resolved and installed");
}

This is equivalent to calling lockfile.get_resource(name).is_some().

Source

pub fn get_resources(&self, resource_type: &ResourceType) -> &[LockedResource]

Get resources by type as slice.

Source

pub fn get_resources_mut( &mut self, resource_type: &ResourceType, ) -> &mut Vec<LockedResource>

Get mutable resources by type.

Source

pub fn all_resources(&self) -> Vec<&LockedResource>

Collect all resources across all types.

Useful for operations processing resources uniformly:

  • Installation reports
  • Checksum validation
  • Bulk operations
§Returns

A vector containing references to all LockedResource entries in the lockfile. The order matches the resource type order defined in crate::core::ResourceType::all().

§Examples
let all_resources = lockfile.all_resources();
println!("Total locked resources: {}", all_resources.len());

for resource in all_resources {
    println!("- {}: {}", resource.name, resource.installed_at);
}
Source

pub fn clear(&mut self)

Clear all entries, returning lockfile to empty state.

Format version unchanged.

§Examples
let mut lockfile = LockFile::new();
// ... add sources and resources ...

lockfile.clear();
assert!(lockfile.sources.is_empty());
assert!(lockfile.agents.is_empty());
assert!(lockfile.snippets.is_empty());
§Use Cases
  • Preparing for complete lockfile regeneration
  • Implementing agpm clean functionality
  • Resetting lockfile state during testing
  • Handling lockfile corruption recovery
Source

pub fn find_resource( &self, name: &str, resource_type: &ResourceType, ) -> Option<&LockedResource>

Find resource by name within specific type.

More precise than get_resource when type is known.

§Arguments
  • name - Resource name to search for
  • resource_type - The type of resource to search within
§Returns
  • Some(&LockedResource) - Reference to the found resource
  • None - No resource with that name exists in the specified type
§Examples
// Find a specific agent
if let Some(agent) = lockfile.find_resource("helper", &ResourceType::Agent) {
    println!("Found agent: {}", agent.installed_at);
}

// Find a specific snippet
if let Some(snippet) = lockfile.find_resource("utils", &ResourceType::Snippet) {
    println!("Found snippet: {}", snippet.installed_at);
}

Note: External callers should prefer find_resource_by_id(&ResourceId) for ResourceId-based lookup.

Source

pub fn find_resource_by_id(&self, id: &ResourceId) -> Option<&LockedResource>

Find resource by complete ResourceId (canonical lookup method).

Checks all identity fields: name, source, tool, template_vars.

§Arguments
  • id - The complete ResourceId to search for
§Returns
  • Some(&LockedResource) - Reference to the matching resource
  • None - No resource with that exact ID exists
§Examples
// Find resource with specific template_vars
let template_vars = json!({"project": {"language": "python"}});
let variant_hash = compute_variant_inputs_hash(&template_vars).unwrap_or_default();
let id = ResourceId::new(
    "backend-engineer",
    Some("community"),
    Some("claude-code"),
    ResourceType::Agent,
    variant_hash
);

if let Some(resource) = lockfile.find_resource_by_id(&id) {
    println!("Found: {}", resource.installed_at);
}
Source

pub fn find_resource_by_id_mut( &mut self, id: &ResourceId, ) -> Option<&mut LockedResource>

Find mutable resource by ResourceId.

Use for modifications (checksums, patches, etc.).

§Arguments
  • id - The complete ResourceId to search for
§Returns
  • Some(&mut LockedResource) - Mutable reference to the matching resource
  • None - No resource with that exact ID exists
Source

pub fn get_resources_by_type( &self, resource_type: &ResourceType, ) -> &[LockedResource]

Get all resources by type for templating.

§Arguments
  • resource_type - The type of resources to retrieve
§Returns

A slice of all resources of the specified type.

§Examples
// Get all agents for templating
let agents = lockfile.get_resources_by_type(&ResourceType::Agent);
for agent in agents {
    println!("Agent: {} -> {}", agent.name, agent.installed_at);
}

// Get all snippets for templating
let snippets = lockfile.get_resources_by_type(&ResourceType::Snippet);
println!("Found {} snippets", snippets.len());
§See Also
Source§

impl LockFile

Source

pub fn validate_against_manifest( &self, manifest: &Manifest, strict: bool, ) -> Result<Option<StalenessReason>>

Validate lockfile against manifest for staleness detection.

Checks consistency and detects staleness indicators requiring regeneration. Similar to Cargo’s --locked mode.

§Arguments
  • manifest - The current project manifest to validate against
  • strict - If true, check version/path changes; if false, only check corruption and security
§Returns
  • Ok(None) - Lockfile is valid and up-to-date
  • Ok(Some(StalenessReason)) - Lockfile is stale and needs regeneration
  • Err(anyhow::Error) - Validation failed due to IO or parse error
§Examples
let lockfile = LockFile::load(Path::new("agpm.lock"))?;
let manifest = Manifest::load(Path::new("agpm.toml"))?;

// Strict mode: check everything including version/path changes
match lockfile.validate_against_manifest(&manifest, true)? {
    None => println!("Lockfile is valid"),
    Some(reason) => {
        eprintln!("Lockfile is stale: {}", reason);
        eprintln!("Run 'agpm install' to auto-update it");
    }
}

// Lenient mode: only check corruption and security (for --frozen)
match lockfile.validate_against_manifest(&manifest, false)? {
    None => println!("Lockfile has no critical issues"),
    Some(reason) => eprintln!("Critical issue: {}", reason),
}
§Staleness Detection

The method checks for several staleness indicators:

  • Duplicate entries: Multiple entries for the same dependency (corruption) - always checked
  • Source URL changes: Source URLs changed in manifest (security concern) - always checked
  • Missing dependencies: Manifest has deps not in lockfile - only in strict mode
  • Version changes: Same dependency with different version constraint - only in strict mode
  • Path changes: Same dependency with different source path - only in strict mode

Note: Extra lockfile entries are allowed (for transitive dependencies).

Source

pub fn is_stale(&self, manifest: &Manifest, strict: bool) -> Result<bool>

Check if lockfile is stale (boolean convenience method).

Returns simple bool instead of detailed StalenessReason.

§Arguments
  • manifest - The current project manifest to validate against
  • strict - If true, check version/path changes; if false, only check corruption and security
§Returns
  • Ok(true) - Lockfile is stale and needs updating
  • Ok(false) - Lockfile is valid and up-to-date
  • Err(anyhow::Error) - Validation failed due to IO or parse error
§Examples
let lockfile = LockFile::load(Path::new("agpm.lock"))?;
let manifest = Manifest::load(Path::new("agpm.toml"))?;

if lockfile.is_stale(&manifest, true)? {
    println!("Lockfile needs updating");
}
Source

pub fn validate_no_duplicates(&self, path: &Path) -> Result<()>

Validate no duplicate names within each resource type.

Stricter than detect_duplicate_entries. Used during loading to catch corruption early.

§Arguments
  • path - Path to the lockfile (used for error messages)
§Returns
  • Ok(()) - No duplicates found
  • Err(anyhow::Error) - Duplicates found with detailed error message
§Errors

Returns an error if any resource type contains duplicate names, with details about which resource type and names are duplicated.

Source§

impl LockFile

Source

pub const fn new() -> Self

Create a new empty lockfile with the current format version.

Returns a fresh lockfile with no sources or resources. This is typically used when initializing a new project or regenerating a lockfile from scratch.

§Examples
use agpm_cli::lockfile::LockFile;

let lockfile = LockFile::new();
assert_eq!(lockfile.version, 1);
assert!(lockfile.sources.is_empty());
assert!(lockfile.agents.is_empty());
assert!(lockfile.snippets.is_empty());
Source§

impl LockFile

Source

pub fn normalize(&self) -> Self

Normalize lockfile entries for backward compatibility.

Converts old lockfile entries that don’t follow the canonical naming convention to use canonical names with manifest_alias. This ensures that lockfiles created before the naming change remain compatible with the new system.

§Returns

A new LockFile with normalized entries

§Examples
let mut lockfile = LockFile::new();
// Load or create entries...
let normalized = lockfile.normalize();
Source

pub fn has_valid_fast_path_metadata(&self) -> bool

Validate the lockfile’s fast-path metadata fields.

Checks consistency of manifest_hash and has_mutable_deps fields:

  • Returns true if both fields are present (valid for fast path)
  • Returns false if either field is missing (cannot use fast path)

This validation is used during installation to determine if the fast path can be safely used. Lockfiles from older AGPM versions may not have these fields, requiring full resolution.

§Returns
  • true if manifest_hash, has_mutable_deps, and resource_count fields are all present
  • false if any field is missing (older lockfile format requires full resolution)
§Examples
let lockfile = LockFile::load(Path::new("agpm.lock")).unwrap();
if lockfile.has_valid_fast_path_metadata() {
    // Can potentially use fast path (still need to check manifest hash match)
} else {
    // Must do full resolution
}
Source

pub fn has_valid_resource_count(&self) -> bool

Validate that the stored resource count matches the actual number of resources.

This detects manual edits to the lockfile that removed resource entries. If the stored count doesn’t match the actual count, the lockfile may have been tampered with and we should fall back to full resolution.

§Note on Variants

When the same resource has multiple variant_inputs (e.g., the same template with different template_vars), each variant is counted as a separate entry. This is intentional since each variant produces a distinct installed file. Adding new variants will legitimately increase the count and trigger re-resolution.

§Returns
  • true if resource count is None (old lockfile) or matches actual count
  • false if stored count differs from actual resource count (tampered lockfile)
Source

pub fn has_valid_manifest_hash_format(&self) -> bool

Validate that manifest_hash format is correct.

The manifest hash should be in the format “sha256:” where is a 64-character lowercase hexadecimal string.

§Returns

true if the manifest_hash is None or has valid format, false if present but invalid.

Source

pub fn split_by_privacy(&self) -> (Self, PrivateLockFile)

Split the lockfile into public and private parts based on is_private flag.

After dependency resolution, resources are partitioned:

  • Public resources (is_private = false) → main agpm.lock
  • Private resources (is_private = true) → agpm.private.lock

This ensures team lockfiles remain consistent while allowing personal private dependencies without conflicts.

§Returns

A tuple of (public_lockfile, private_lockfile).

§Examples
let combined = LockFile::new();
// ... resolve dependencies ...
let (public_lock, private_lock) = combined.split_by_privacy();

// Save to separate files
public_lock.save(Path::new("agpm.lock"))?;
private_lock.save(Path::new("."))?;
Source

pub fn merge_private(&mut self, private_lock: &PrivateLockFile)

Merge private resources from a PrivateLockFile into this lockfile.

When loading lockfiles, first load agpm.lock, then optionally merge agpm.private.lock to get the complete set of resources for this user.

Private resources are added to the appropriate resource type vectors. Duplicates (same name, source, tool, variant_hash) are not checked; the caller is responsible for ensuring no conflicts.

§Examples
let mut lockfile = LockFile::load(Path::new("agpm.lock"))?;
if let Some(private_lock) = PrivateLockFile::load(Path::new("."))? {
    lockfile.merge_private(&private_lock);
}

Trait Implementations§

Source§

impl Clone for LockFile

Source§

fn clone(&self) -> LockFile

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for LockFile

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for LockFile

Source§

fn default() -> Self

Equivalent to LockFile::new() - creates empty lockfile with current format version.

Source§

impl<'de> Deserialize<'de> for LockFile

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Serialize for LockFile

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,