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