Skip to main content

agentshield/adapter/
mod.rs

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