Skip to main content

agentshield/adapter/
mod.rs

1pub mod crewai;
2pub mod cursor_rules;
3pub mod gpt_actions;
4pub mod hermes;
5pub mod langchain;
6pub mod mcp;
7pub(super) mod mcp_metadata;
8pub mod openclaw;
9
10use std::path::Path;
11
12use crate::config::ScanPathFilter;
13use crate::error::{Result, ShieldError};
14use crate::ir::{Framework, ScanTarget};
15
16/// An adapter detects a specific agent framework and loads its artifacts
17/// into the unified IR.
18pub trait Adapter: Send + Sync {
19    /// The framework this adapter handles.
20    fn framework(&self) -> Framework;
21
22    /// Check if this adapter can handle the given directory.
23    fn detect(&self, root: &Path) -> bool;
24
25    /// Load artifacts from the directory into scan targets.
26    /// When `ignore_tests` is true, test files are excluded before parsing.
27    fn load(&self, root: &Path, ignore_tests: bool) -> Result<Vec<ScanTarget>>;
28
29    fn load_with_filter(&self, root: &Path, filter: &ScanPathFilter) -> Result<Vec<ScanTarget>> {
30        self.load(root, filter.ignore_tests())
31    }
32}
33
34/// All registered adapters.
35pub fn all_adapters() -> Vec<Box<dyn Adapter>> {
36    vec![
37        Box::new(mcp::McpAdapter),
38        Box::new(openclaw::OpenClawAdapter),
39        Box::new(hermes::HermesAgentAdapter),
40        Box::new(crewai::CrewAiAdapter),
41        Box::new(langchain::LangChainAdapter),
42        Box::new(gpt_actions::GptActionsAdapter),
43        Box::new(cursor_rules::CursorRulesAdapter),
44    ]
45}
46
47/// Auto-detect all matching frameworks and load scan targets from each.
48///
49/// Repos may contain both MCP and OpenClaw artifacts — all matching
50/// adapters contribute targets rather than stopping at the first match.
51pub fn auto_detect_and_load(root: &Path, ignore_tests: bool) -> Result<Vec<ScanTarget>> {
52    let filter = ScanPathFilter::for_ignore_tests(ignore_tests);
53    auto_detect_and_load_with_filter(root, &filter)
54}
55
56pub fn auto_detect_and_load_with_filter(
57    root: &Path,
58    filter: &ScanPathFilter,
59) -> Result<Vec<ScanTarget>> {
60    let adapters = all_adapters();
61    let mut all_targets = Vec::new();
62
63    for adapter in &adapters {
64        if adapter.detect(root) {
65            match adapter.load_with_filter(root, filter) {
66                Ok(targets) => all_targets.extend(targets),
67                Err(e) => {
68                    tracing::warn!(
69                        framework = %adapter.framework(),
70                        error = %e,
71                        "adapter failed to load, skipping"
72                    );
73                }
74            }
75        }
76    }
77
78    if all_targets.is_empty() {
79        return Err(ShieldError::NoAdapter(root.display().to_string()));
80    }
81
82    Ok(all_targets)
83}