RedundancyDetector

Struct RedundancyDetector 

Source
pub struct RedundancyDetector { /* private fields */ }
Expand description

Analyzes dependency patterns to detect and categorize redundancies.

The RedundancyDetector is the main analysis engine for identifying optimization opportunities in dependency manifests. It builds a comprehensive view of how resources use source files and identifies patterns that might indicate redundancy.

§Analysis Process

  1. Collection: Gather all resource usages via add_usage() or analyze_manifest()
  2. Detection: Run detect_redundancies() to find redundant patterns
  3. Reporting: Generate warnings or suggestions using helper methods

§Thread Safety

The detector is not thread-safe due to mutable state during analysis. Create separate instances for concurrent analysis operations.

§Memory Usage

The detector maintains an in-memory map of all resource usages. For large manifests with hundreds of dependencies, memory usage scales linearly:

  • Each resource usage: ~100 bytes (strings + metadata)
  • HashMap overhead: ~25% of total usage data

Implementations§

Source§

impl RedundancyDetector

Source

pub fn new() -> Self

Creates a new redundancy detector with empty state.

The detector starts with no resource usage data. Use add_usage() for individual dependencies or analyze_manifest() for complete manifest analysis.

§Example
use agpm_cli::resolver::redundancy::RedundancyDetector;

let mut detector = RedundancyDetector::new();
// Add usages or analyze manifest...
let redundancies = detector.detect_redundancies();
Source

pub fn add_usage(&mut self, resource_name: String, dep: &ResourceDependency)

Records a resource usage for redundancy analysis.

This method adds a single resource dependency to the analysis dataset. Local dependencies are automatically filtered out since they don’t have redundancy concerns (each local path is unique).

§Filtering Logic
  • Remote Dependencies: Added to analysis (have source + path)
  • Local Dependencies: Skipped (path-only, no redundancy issues)
  • Invalid Dependencies: Skipped (missing source information)
§Source File Identification

Remote dependencies are identified by their composite key:

source_file = "{source_name}:{resource_path}"

This ensures that the same file from different sources is treated as separate resources (no cross-source redundancy detection yet).

§Parameters
  • resource_name: Name assigned to this resource in the manifest
  • dep: Resource dependency specification from manifest
§Example
use agpm_cli::resolver::redundancy::RedundancyDetector;
use agpm_cli::manifest::{ResourceDependency, DetailedDependency};

let mut detector = RedundancyDetector::new();

// This will be recorded
let remote_dep = ResourceDependency::Detailed(Box::new(DetailedDependency {
    source: Some("community".to_string()),
    path: "agents/helper.md".to_string(),
    version: Some("v1.0.0".to_string()),
    branch: None,
    rev: None,
    command: None,
    args: None,
    target: None,
    filename: None,
    dependencies: None,
    tool: "claude-code".to_string(),
}));
detector.add_usage("my-helper".to_string(), &remote_dep);

// This will be ignored (local dependency)
let local_dep = ResourceDependency::Simple("../local/helper.md".to_string());
detector.add_usage("local-helper".to_string(), &local_dep);
Source

pub fn analyze_manifest(&mut self, manifest: &Manifest)

Analyzes all dependencies from a manifest for redundancy patterns.

This is a convenience method that processes all dependencies from a manifest file in a single operation. It’s equivalent to calling add_usage() for each dependency individually.

§Processing Scope

The method analyzes:

  • Agent Dependencies: From [agents] section
  • Snippet Dependencies: From [snippets] section
  • Remote Dependencies: Only those with source specifications

Local dependencies are automatically filtered out during analysis.

§Usage Pattern

This method is typically used in the main resolution workflow:

use agpm_cli::resolver::redundancy::RedundancyDetector;
use agpm_cli::manifest::Manifest;
use std::path::Path;

let manifest = Manifest::load(Path::new("agpm.toml"))?;
let mut detector = RedundancyDetector::new();
detector.analyze_manifest(&manifest);

let redundancies = detector.detect_redundancies();
let warning = detector.generate_redundancy_warning(&redundancies);
if !warning.is_empty() {
    eprintln!("{}", warning);
}
§Performance
  • Time Complexity: O(n) where n = total dependencies
  • Space Complexity: O(r) where r = remote dependencies
  • Memory Usage: Linear with number of remote dependencies
Source

pub fn detect_redundancies(&self) -> Vec<Redundancy>

Detects redundancy patterns in the collected resource usages.

This method analyzes all collected resource usages and identifies patterns where multiple resources use the same source file with different version constraints.

§Detection Algorithm

For each source file in the usage map:

  1. Skip Single Usage: Files used by only one resource are not redundant
  2. Version Analysis: Collect all unique version constraints for the file
  3. Redundancy Check: If multiple different versions exist, mark as redundant
§Redundancy Criteria

A source file is considered redundant when:

  • Multiple Resources: More than one resource uses the file
  • Different Versions: Resources specify different version constraints
§Non-Redundant Cases

These cases are NOT considered redundant:

  • Single resource using a source file
  • Multiple resources using identical version constraints
  • Multiple resources all using “latest” (no version specified)
§Algorithm Complexity
  • Time: O(n + k·m) where:
    • n = total resource usages
    • k = unique source files
    • m = average usages per file
  • Space: O(r) where r = detected redundancies
§Returns

A vector of Redundancy objects, each representing a source file with redundant usage patterns. The vector is empty if no redundancies are detected.

§Example Output

For a manifest with redundant dependencies, this method might return:

[
    Redundancy {
        source_file: "community:agents/helper.md",
        usages: [
            ResourceUsage { resource_name: "app-helper", version: Some("v1.0.0") },
            ResourceUsage { resource_name: "tool-helper", version: Some("v2.0.0") },
        ]
    }
]
Source

pub fn can_consolidate(&self, redundancy: &Redundancy) -> bool

Determines if a redundancy could be consolidated to use a single version.

This method analyzes a detected redundancy to determine if all resources using the source file could reasonably be updated to use the same version. This is a heuristic for suggesting consolidation opportunities.

§Consolidation Logic

A redundancy can be consolidated if:

  • All resources use the same version constraint (already consolidated)
  • All resources use “latest” (no specific versions)

A redundancy cannot be easily consolidated if:

  • Resources use different specific versions (may have compatibility reasons)
  • Mixed latest and specific versions (may indicate intentional pinning)
§Use Cases

This method helps identify:

  • Easy Wins: Redundancies that could be quickly resolved
  • Complex Cases: Redundancies that may require careful consideration
  • Intentional Patterns: Cases where redundancy might be deliberate
§Parameters
  • redundancy: The redundancy pattern to analyze
§Returns
  • true: All usages could likely be consolidated to a single version
  • false: Consolidation would require careful analysis of compatibility
§Example
use agpm_cli::resolver::redundancy::{RedundancyDetector, Redundancy, ResourceUsage};

let detector = RedundancyDetector::new();

// Easy to consolidate (all use latest)
let easy_redundancy = Redundancy {
    source_file: "community:agents/helper.md".to_string(),
    usages: vec![
        ResourceUsage { resource_name: "helper1".to_string(), source_file: "community:agents/helper.md".to_string(), version: None },
        ResourceUsage { resource_name: "helper2".to_string(), source_file: "community:agents/helper.md".to_string(), version: None },
    ]
};
assert!(detector.can_consolidate(&easy_redundancy));

// Hard to consolidate (different versions)
let hard_redundancy = Redundancy {
    source_file: "community:agents/helper.md".to_string(),
    usages: vec![
        ResourceUsage { resource_name: "helper1".to_string(), source_file: "community:agents/helper.md".to_string(), version: Some("v1.0.0".to_string()) },
        ResourceUsage { resource_name: "helper2".to_string(), source_file: "community:agents/helper.md".to_string(), version: Some("v2.0.0".to_string()) },
    ]
};
assert!(!detector.can_consolidate(&hard_redundancy));
Source

pub fn generate_redundancy_warning(&self, redundancies: &[Redundancy]) -> String

Generates a comprehensive warning message for detected redundancies.

This method creates a user-friendly warning message that explains detected redundancies and provides actionable suggestions for optimization. The message is designed to be informative rather than alarming, emphasizing that redundancy is not an error.

§Message Structure

The generated warning includes:

  1. Header: Clear indication this is a warning, not an error
  2. Redundancy List: Each detected redundancy with details
  3. General Guidance: Explanation of implications and options
  4. Specific Suggestions: Targeted advice based on detected patterns
§Message Tone

The warning message maintains a helpful, non-blocking tone:

  • Emphasizes that installation will proceed normally
  • Explains that redundancy may be intentional
  • Provides optimization suggestions without mandating changes
  • Uses clear, jargon-free language
§Color Coding

The message uses terminal colors for better readability:

  • Yellow: Warning indicators and attention markers
  • Blue: Informational notes and suggestions
  • Default: Main content and resource names
§Parameters
  • redundancies: List of detected redundancy patterns
§Returns
  • Non-empty: Formatted warning message if redundancies exist
  • Empty string: If no redundancies provided
§Example Output
Warning: Redundant dependencies detected

⚠ Multiple versions of 'community:agents/helper.md' will be installed:
  - 'app-helper' uses version v1.0.0
  - 'tool-helper' uses version latest

Note: This is not an error, but you may want to consider:
  • Using the same version for consistency
  • These resources will be installed to different files
  • Each will work independently
  • Consider aligning versions for 'community:agents/helper.md' across all resources
Source

pub const fn check_transitive_redundancies(&self) -> Vec<Redundancy>

Placeholder for future transitive redundancy detection.

This method is reserved for future implementation when AGPM supports dependencies-of-dependencies (transitive dependencies). Currently returns an empty vector as transitive analysis is not yet implemented.

§Planned Functionality

When implemented, this method will:

  1. Build Dependency Tree: Map entire transitive dependency graph
  2. Detect Deep Redundancy: Find redundant patterns across dependency levels
  3. Analyze Impact: Calculate storage and maintenance implications
  4. Suggest Optimizations: Recommend dependency tree restructuring
§Example Future Analysis
Direct:     app-agent → community:agents/helper.md v1.0.0
Transitive: app-agent → tool-lib → community:agents/helper.md v2.0.0

Result: Transitive redundancy detected - app-agent indirectly depends
        on two versions of the same resource.
§Implementation Challenges
  • Circular Dependencies: Detection and handling of cycles
  • Version Compatibility: Analyzing semantic version compatibility
  • Performance: Efficient analysis of large dependency trees
  • Cache Management: Handling cached vs. fresh transitive data
§Returns

Currently returns an empty vector. Future implementation will return detected transitive redundancies.

Source

pub fn suggest_consolidation(&self, redundancy: &Redundancy) -> Vec<String>

Generates actionable consolidation strategies for a specific redundancy.

This method analyzes a detected redundancy pattern and provides specific, actionable suggestions for resolving or managing the redundancy. The suggestions are tailored to the specific pattern of version usage.

§Strategy Categories
§Version Alignment

For redundancies with multiple specific versions:

  • Suggest adopting a single version across all resources
  • Recommend the most recent or most commonly used version
§Constraint Standardization

For mixed latest/specific version patterns:

  • Suggest using specific versions for reproducibility
  • Explain benefits of version pinning
§Impact Assessment

For all redundancies:

  • Clarify that resources will be installed independently
  • Explain that each resource will function correctly
  • List all affected resource names
§Suggestion Algorithm
  1. Analyze Version Pattern: Identify specific vs. latest usage
  2. Generate Alignment Suggestions: Recommend version standardization
  3. Provide Context: Explain implications and benefits
  4. List Affected Resources: Show impact scope
§Parameters
  • redundancy: The redundancy pattern to analyze
§Returns

A vector of suggestion strings, ordered by priority:

  1. Primary suggestions (version alignment)
  2. Best practice recommendations (reproducibility)
  3. Impact clarification (what will actually happen)
§Example Output

For a redundancy with mixed version constraints:

"Consider using version v2.0.0 for all resources using 'community:agents/helper.md'"
"Consider using specific versions for all resources for reproducibility"
"Note: Each resource (app-helper, tool-helper) will be installed independently"
§Use Cases
  • CLI Tools: Generate help text for redundancy warnings
  • IDE Extensions: Provide quick-fix suggestions
  • Automated Tools: Implement dependency optimization utilities
  • Documentation: Generate project-specific optimization guides

Trait Implementations§

Source§

impl Default for RedundancyDetector

Source§

fn default() -> Self

Returns the “default value” for a type. 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> 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> 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, 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<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> ErasedDestructor for T
where T: 'static,