pub fn extract_relative_path(
path: &Path,
resource_type: &ResourceType,
) -> PathBufExpand description
Extracts the relative path from a resource by removing the resource type directory prefix.
This function preserves directory structure when installing resources from Git sources by intelligently stripping the resource type directory (e.g., “agents/”, “snippets/”) from source repository paths. This allows subdirectories within a resource category to be maintained in the installation target, enabling organized source repositories to retain their structure.
§Path Processing Strategy
The function implements a prefix-aware extraction algorithm:
- Converts the resource type string to its expected directory name (e.g., “agent” → “agents”)
- Checks if the path starts with this directory name as its first component
- If matched, returns the path with the first component stripped
- If not matched, returns the original path unchanged
This approach ensures that:
- Nested directories within a category are preserved (e.g.,
agents/ai/helper.md→ai/helper.md) - Paths without the expected prefix remain unchanged (backwards compatibility)
- Cross-platform path handling works correctly (Windows and Unix separators)
§Arguments
path- The original resource path from the dependency specification (e.g., from a Git repository)resource_type- The resource type string:"agent","snippet","command","script","hook", or"mcp-server"
§Returns
A PathBuf containing:
- The path with the resource type prefix removed (if the prefix matched)
- The original path unchanged (if no prefix matched)
§Resource Type Mapping
| Input Type | Expected Directory | Example Input Path | Example Output Path |
|---|---|---|---|
"agent" | agents/ | agents/ai/helper.md | ai/helper.md |
"snippet" | snippets/ | snippets/tools/fmt.md | tools/fmt.md |
"command" | commands/ | commands/build.md | build.md |
"script" | scripts/ | scripts/test.sh | test.sh |
"hook" | hooks/ | hooks/pre-commit.json | pre-commit.json |
"mcp-server" | mcp-servers/ | mcp-servers/db.json | db.json |
§Examples
§Basic Path Extraction
use std::path::{Path, PathBuf};
// Resource type prefix is removed
let path = Path::new("snippets/directives/thing.md");
let result = extract_relative_path(path, &ResourceType::Snippet);
assert_eq!(result, PathBuf::from("directives/thing.md"));
// No matching prefix - path unchanged
let path = Path::new("directives/thing.md");
let result = extract_relative_path(path, &ResourceType::Snippet);
assert_eq!(result, PathBuf::from("directives/thing.md"));
// Works with deeply nested directories
let path = Path::new("agents/ai/helper.md");
let result = extract_relative_path(path, &ResourceType::Agent);
assert_eq!(result, PathBuf::from("ai/helper.md"));§Preserving Directory Structure
// Multi-level nested directories are fully preserved
let path = Path::new("agents/languages/rust/expert.md");
let result = extract_relative_path(path, &ResourceType::Agent);
assert_eq!(result, PathBuf::from("languages/rust/expert.md"));
// This will install to: .claude/agents/languages/rust/expert.md§Pattern Matching Use Case
When used with glob patterns like agents/**/*.md, this function ensures each
matched file preserves its subdirectory structure:
// Example: glob pattern "agents/**/*.md" matches these paths
let matched_paths = vec![
"agents/rust/expert.md",
"agents/rust/testing.md",
"agents/python/async.md",
"agents/go/concurrency.md",
];
for path_str in matched_paths {
let path = Path::new(path_str);
let relative = extract_relative_path(path, &ResourceType::Agent);
// Produces: "rust/expert.md", "rust/testing.md", "python/async.md", "go/concurrency.md"
// Each installs to: .claude/agents/<relative_path>
}§Integration with Custom Targets
Custom targets work in conjunction with relative path extraction:
# In agpm.toml
[agents]
# Path: agents/rust/expert.md → extract → rust/expert.md
# Target: custom → combined → custom/rust/expert.md
# Final: .claude/agents/custom/rust/expert.md
rust-agents = {
source = "community",
path = "agents/rust/*.md",
target = "custom",
version = "v1.0.0"
}§Use Cases
§Organized Source Repository
For a source repository with categorized resources:
agpm-community/
├── agents/
│ ├── languages/
│ │ ├── rust/
│ │ │ ├── expert.md
│ │ │ └── testing.md
│ │ └── python/
│ │ └── async.md
│ └── tools/
│ └── git-helper.md
└── snippets/
├── directives/
│ └── custom.md
└── templates/
└── api.mdAfter installation, the structure is preserved:
.claude/
├── agents/
│ ├── languages/
│ │ ├── rust/
│ │ │ ├── expert.md
│ │ │ └── testing.md
│ │ └── python/
│ │ └── async.md
│ └── tools/
│ └── git-helper.md
└── snippets/
├── directives/
│ └── custom.md
└── templates/
└── api.md§Pattern-Based Installation
Bulk installation with patterns preserves organization:
[agents]
# Installs all Rust agents with subdirectory structure intact
rust-tools = { source = "community", path = "agents/languages/rust/**/*.md", version = "v1.0.0" }
# Results in: .claude/agents/<files from rust/ and subdirectories>§Implementation Notes
- Uses path component analysis for cross-platform compatibility
- Only examines the first path component to determine prefix match
- Empty paths or invalid components are handled gracefully
- Unknown resource types cause the path to be returned unchanged
- Works with both absolute and relative paths from source repositories
§Version History
- v0.3.18: Introduced to support relative path preservation during installation
- Works in conjunction with updated lockfile
installed_atpath generation