Skip to content

Add multi-framework adapters and auto-discovery for 12 AI agents#68

Merged
debu-sinha merged 6 commits into
mainfrom
feature/multi-framework
Apr 1, 2026
Merged

Add multi-framework adapters and auto-discovery for 12 AI agents#68
debu-sinha merged 6 commits into
mainfrom
feature/multi-framework

Conversation

@debu-sinha
Copy link
Copy Markdown
Owner

Closes #53, closes #55

What changed

Multi-Framework Adapters (#55)

  • adapters/base.py: FrameworkAdapter ABC + FrameworkConfig with 6 typed sub-configs
  • adapters/claude_code.py: Claude Code adapter (settings.json, hooks, .mcp.json, CLAUDE.md, rules, agents, commands)
  • adapters/cursor.py: Cursor adapter (.cursorrules, .cursor/rules/*.mdc, mcp.json, tasks.json auto-run detection)
  • adapters/windsurf.py: Windsurf adapter (.windsurfrules, mcp_config.json, dual path ~/.windsurf + ~/.codeium/windsurf)
  • adapters/vscode_copilot.py: VS Code Copilot adapter (copilot-instructions.md, AGENTS.md, .vscode/mcp.json with "servers" key, tasks.json)

Auto-Discovery (#53)

  • models/agents.py: AgentDefinition registry for 12 agents (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code Copilot, Gemini CLI, OpenAI Codex, OpenClaw, Amazon Q, Kiro, Continue, Roo Code) with OS-specific paths
  • discovery.py: Filesystem marker scanning, binary detection, MCP config enumeration
  • cli.py: Added agentsec discover command with --detect-versions and --json flags

Architecture decisions

Per expert review:

  • FrameworkConfig wraps ScanContext (not replaces)
  • Existing OpenClaw checks untouched (migration is follow-up)
  • Version detection gated behind --detect-versions (no binary execution by default)
  • Shared AgentDefinition model consumed by both discovery and adapters

Testing

Lint passes (ruff check + ruff format). Pyright unresolved imports are expected (agentsec not in IDE venv). Manual testing of discovery and adapters needed.

Shared data model for auto-discovery and multi-framework scanning.
Defines marker paths, config paths, MCP config paths, and binary
names for Claude Code, Claude Desktop, Cursor, Windsurf, VS Code
Copilot, Gemini CLI, OpenAI Codex, OpenClaw, Amazon Q, Kiro,
Continue, and Roo Code across macOS, Linux, and Windows.

Signed-off-by: debu-sinha <debusinha2009@gmail.com>
Framework adapters (#55): FrameworkAdapter ABC with typed FrameworkConfig
dataclass. Adapters for Claude Code, Cursor, Windsurf, and VS Code
Copilot normalize configs into a unified model for cross-framework
security checks.

Auto-discovery (#53): discover_agents() scans filesystem for 12 known
AI agent installations. CLI command: agentsec discover. Version
detection opt-in via --detect-versions flag.

New files: adapters/base.py (157), adapters/claude_code.py (485),
adapters/cursor.py (391), adapters/windsurf.py (261),
adapters/vscode_copilot.py (310), discovery.py (206),
models/agents.py (401). Total: 2,211 lines.

Signed-off-by: debu-sinha <debusinha2009@gmail.com>
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix unreachable except blocks caused by ordering, place more specific exceptions before more general ones, or remove the redundant handler if special handling is not needed. Here, _read_json returns None for all error conditions; the only difference between the OSError and PermissionError handlers is the log message. _read_text in the same file has the same structural issue and also treats errors uniformly, with only logging differing.

The simplest fix that preserves existing behavior is to remove the unreachable except PermissionError: blocks entirely. All PermissionErrors are already handled by the OSError clause (as PermissionError is a subclass of OSError), which logs an error-specific message and returns None. Deleting the redundant blocks does not change runtime behavior but eliminates the CodeQL warning and the dead code. Concretely:

  • In _read_json (around lines 70–78), delete the except PermissionError: block.
  • In _read_text (around lines 85–90), delete the except PermissionError: block.
    No new imports or helper methods are needed.
Suggested changeset 1
src/agentsec/adapters/claude_code.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/claude_code.py b/src/agentsec/adapters/claude_code.py
--- a/src/agentsec/adapters/claude_code.py
+++ b/src/agentsec/adapters/claude_code.py
@@ -73,9 +73,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 def _read_text(path: Path) -> str | None:
@@ -85,9 +82,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 class ClaudeCodeAdapter(FrameworkAdapter):
EOF
@@ -73,9 +73,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


def _read_text(path: Path) -> str | None:
@@ -85,9 +82,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


class ClaudeCodeAdapter(FrameworkAdapter):
Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix unreachable except blocks where a more specific exception comes after a more general one, you either (a) reorder the except clauses so the more specific exception is listed first, or (b) remove the redundant specific handler if it doesn’t need distinct behavior. The goal is to ensure each except block is either reachable or eliminated.

For this codebase, the best way to fix the problem without changing existing functionality is to mirror the pattern already used in _read_json. In _read_json, PermissionError is handled after OSError, which is technically redundant/unreachable as well. However, functionally, both _read_json and _read_text simply log a debug message and return None for both OSError and PermissionError. Since _read_text’s PermissionError handler logs a slightly different message, but PermissionError is already subsumed by OSError, the simplest non‑behavior‑changing fix is to remove the unreachable except PermissionError: block from _read_text and let PermissionError be handled by the existing OSError block. This preserves overall behavior except for the exact debug message for permission errors in _read_text, which currently never executes anyway, so there is no user-visible behavior to preserve.

Concretely:

  • Edit src/agentsec/adapters/claude_code.py.
  • In _read_text, delete the except PermissionError: clause and its body (lines 88–90 in the snippet).
  • Leave _read_json unchanged, since the issue reported is specifically for _read_text, and we must minimize changes.

No new imports, methods, or other definitions are required.

Suggested changeset 1
src/agentsec/adapters/claude_code.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/claude_code.py b/src/agentsec/adapters/claude_code.py
--- a/src/agentsec/adapters/claude_code.py
+++ b/src/agentsec/adapters/claude_code.py
@@ -85,11 +85,9 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
+
 class ClaudeCodeAdapter(FrameworkAdapter):
     """Adapter for Claude Code agent framework configuration."""
 
EOF
@@ -85,11 +85,9 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None



class ClaudeCodeAdapter(FrameworkAdapter):
"""Adapter for Claude Code agent framework configuration."""

Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, this issue is fixed by making sure more specific except clauses (for subclasses) come before more general ones (for superclasses), or by removing redundant handlers that are subsumed by a more general one. Here, both the OSError and PermissionError handlers in _read_json only log a debug message and return None, so there is no functional distinction between them. The same is true in _read_text. Therefore, the minimal, behavior-preserving change is to remove the unreachable except PermissionError: blocks in both functions and rely on the existing OSError handler to cover PermissionError as well.

Concretely:

  • In src/agentsec/adapters/cursor.py, within _read_json, delete lines 61–63 (except PermissionError: and its body).
  • In the same file, within _read_text, delete lines 73–75 (except PermissionError: and its body).

No imports or new helpers are required; we only remove dead code. All errors, including permission issues, will still be logged (via the OSError handler) and result in None, which matches the current public behavior.

Suggested changeset 1
src/agentsec/adapters/cursor.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/cursor.py b/src/agentsec/adapters/cursor.py
--- a/src/agentsec/adapters/cursor.py
+++ b/src/agentsec/adapters/cursor.py
@@ -58,9 +58,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 def _read_text(path: Path) -> str | None:
@@ -70,9 +67,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 def _parse_mdc(content: str, source_file: str) -> RuleConfig | None:
EOF
@@ -58,9 +58,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


def _read_text(path: Path) -> str | None:
@@ -70,9 +67,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


def _parse_mdc(content: str, source_file: str) -> RuleConfig | None:
Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix unreachable except blocks caused by ordering, you either (1) reorder the handlers to put more specific exceptions before more general ones, or (2) remove the redundant handler if its behavior is not needed or is already covered by the more general block.

Here, _read_text already logs and returns None in both the OSError and PermissionError handlers, so the PermissionError block is redundant; the OSError handler already covers it with identical behavior. The simplest fix that preserves functionality is to delete the unreachable except PermissionError: clause and let PermissionError be handled by the OSError handler.

Concretely, in src/agentsec/adapters/cursor.py, within the _read_text function, remove lines 73–75:

    except PermissionError:
        logger.debug("Permission denied reading %s", path)
        return None

No new imports or helper methods are required.

Suggested changeset 1
src/agentsec/adapters/cursor.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/cursor.py b/src/agentsec/adapters/cursor.py
--- a/src/agentsec/adapters/cursor.py
+++ b/src/agentsec/adapters/cursor.py
@@ -70,9 +70,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 def _parse_mdc(content: str, source_file: str) -> RuleConfig | None:
EOF
@@ -70,9 +70,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


def _parse_mdc(content: str, source_file: str) -> RuleConfig | None:
Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix unreachable except blocks, ensure that more specific exception types appear before more general ones, or remove redundant handlers if their behavior is identical to the broader handler.

In this file, _read_json currently has three handlers: json.JSONDecodeError, OSError, and PermissionError. Because PermissionError is a subclass of OSError, the except PermissionError: block is unreachable. There are two reasonable options:

  1. If you want distinct logging for permission issues, move the PermissionError handler above the OSError handler.
  2. If you are fine with permission errors being treated like other OSErrors, remove the dedicated PermissionError handler.

The _read_text function below already follows pattern (2): it only has an OSError handler and no separate PermissionError handler. To keep behavior consistent across helpers and avoid changing runtime behavior, the minimal, safest fix is to remove the unreachable except PermissionError: block from _read_json, leaving OSError to handle all filesystem-related errors as it already does. No new imports or helper methods are needed; only the body of _read_json in src/agentsec/adapters/vscode_copilot.py should be edited.

Suggested changeset 1
src/agentsec/adapters/vscode_copilot.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/vscode_copilot.py b/src/agentsec/adapters/vscode_copilot.py
--- a/src/agentsec/adapters/vscode_copilot.py
+++ b/src/agentsec/adapters/vscode_copilot.py
@@ -55,9 +55,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 def _read_text(path: Path) -> str | None:
EOF
@@ -55,9 +55,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


def _read_text(path: Path) -> str | None:
Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix an unreachable except block where a subclass-specific handler appears after a superclass handler, you either reorder the handlers so the more specific one comes first, or remove the redundant handler if the specific handling is not needed. The choice depends on whether the code truly needs distinct behavior for that subclass.

Here, _read_text currently logs all OSError (including permission errors) via a generic message, and then has an unreachable PermissionError handler with a more specific log message. The simplest fix that preserves intent and maintains consistency with _read_json is to reverse the order of the two handlers: first catch PermissionError, then catch any remaining OSError. This keeps behavior for non-permission OSError unchanged and enables the more precise logging for permission errors, matching the pattern already used in _read_json.

Concretely, in src/agentsec/adapters/vscode_copilot.py, in the _read_text function, swap the order of the except OSError as e: and except PermissionError: blocks, without changing their bodies. No new imports or helper methods are required; we only reorder the existing except clauses.

Suggested changeset 1
src/agentsec/adapters/vscode_copilot.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/vscode_copilot.py b/src/agentsec/adapters/vscode_copilot.py
--- a/src/agentsec/adapters/vscode_copilot.py
+++ b/src/agentsec/adapters/vscode_copilot.py
@@ -64,12 +64,12 @@
     """Read a text file, returning None on any error."""
     try:
         return path.read_text(errors="replace")
-    except OSError as e:
-        logger.debug("Could not read %s: %s", path, e)
-        return None
     except PermissionError:
         logger.debug("Permission denied reading %s", path)
         return None
+    except OSError as e:
+        logger.debug("Could not read %s: %s", path, e)
+        return None
 
 
 class VSCodeCopilotAdapter(FrameworkAdapter):
EOF
@@ -64,12 +64,12 @@
"""Read a text file, returning None on any error."""
try:
return path.read_text(errors="replace")
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None


class VSCodeCopilotAdapter(FrameworkAdapter):
Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix an unreachable except block caused by ordering, you must ensure that more specific exceptions (subclasses) are listed before more general ones (superclasses), or remove the redundant specific handler if it is not needed. Python will then match the specific handler first, falling back to the general one when appropriate.

For this file, the minimal, behavior-preserving fix is to reorder the two except clauses in _read_json so that except PermissionError: appears before except OSError as e:. This change does not alter the return value (None in both cases) but makes the specialized logging for permission errors actually execute when a PermissionError occurs. No additional imports or helper methods are needed, and no other parts of the file need to change. _read_text already has the same structural issue (PermissionError after OSError), so it should be fixed in the same way for consistency and to avoid future similar alerts.

Concretely:

  • In src/agentsec/adapters/windsurf.py, in _read_json, swap the order of the except OSError as e: and except PermissionError: blocks.
  • In _read_text, swap the order of its except OSError as e: and except PermissionError: blocks as well.
    These edits keep existing functionality while eliminating unreachable code and aligning with the recommendation to put specific handlers first.
Suggested changeset 1
src/agentsec/adapters/windsurf.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/windsurf.py b/src/agentsec/adapters/windsurf.py
--- a/src/agentsec/adapters/windsurf.py
+++ b/src/agentsec/adapters/windsurf.py
@@ -50,24 +50,24 @@
     except json.JSONDecodeError:
         logger.debug("Malformed JSON in %s", path)
         return None
-    except OSError as e:
-        logger.debug("Could not read %s: %s", path, e)
-        return None
     except PermissionError:
         logger.debug("Permission denied reading %s", path)
         return None
+    except OSError as e:
+        logger.debug("Could not read %s: %s", path, e)
+        return None
 
 
 def _read_text(path: Path) -> str | None:
     """Read a text file, returning None on any error."""
     try:
         return path.read_text(errors="replace")
-    except OSError as e:
-        logger.debug("Could not read %s: %s", path, e)
-        return None
     except PermissionError:
         logger.debug("Permission denied reading %s", path)
         return None
+    except OSError as e:
+        logger.debug("Could not read %s: %s", path, e)
+        return None
 
 
 class WindsurfAdapter(FrameworkAdapter):
EOF
@@ -50,24 +50,24 @@
except json.JSONDecodeError:
logger.debug("Malformed JSON in %s", path)
return None
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None


def _read_text(path: Path) -> str | None:
"""Read a text file, returning None on any error."""
try:
return path.read_text(errors="replace")
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None


class WindsurfAdapter(FrameworkAdapter):
Copilot is powered by AI and may make mistakes. Always verify output.
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:

Check failure

Code scanning / CodeQL

Unreachable `except` block Error

This except block handling
PermissionError
is unreachable; as
this except block
for the more general
OSError
always subsumes it.

Copilot Autofix

AI about 2 months ago

In general, to fix unreachable except blocks caused by ordering, either (1) reorder the except clauses so that more specific exceptions come before more general ones, or (2) remove the redundant except clause if its behavior is effectively covered by the more general handler.

Here, _read_text’s except PermissionError: logs essentially the same type of informational message (“Permission denied reading …”) as _read_json does, but _read_text already has an except OSError as e: that logs a generic “Could not read …” message. The simplest, behavior-preserving change is to remove the except PermissionError: block from _read_text, letting PermissionError be handled by the existing OSError block. This mirrors the already-accepted behavior in _read_json, where PermissionError is also a subclass of OSError and thus handled by the OSError clause; the only functional difference is the exact log message, and currently _read_text never reaches its PermissionError block anyway.

Concretely:

  • Edit src/agentsec/adapters/windsurf.py.
  • In _read_text, delete the except PermissionError: clause and its body (lines 68–70 in the provided snippet).
  • No new imports or helper methods are needed.
Suggested changeset 1
src/agentsec/adapters/windsurf.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/agentsec/adapters/windsurf.py b/src/agentsec/adapters/windsurf.py
--- a/src/agentsec/adapters/windsurf.py
+++ b/src/agentsec/adapters/windsurf.py
@@ -65,9 +65,6 @@
     except OSError as e:
         logger.debug("Could not read %s: %s", path, e)
         return None
-    except PermissionError:
-        logger.debug("Permission denied reading %s", path)
-        return None
 
 
 class WindsurfAdapter(FrameworkAdapter):
EOF
@@ -65,9 +65,6 @@
except OSError as e:
logger.debug("Could not read %s: %s", path, e)
return None
except PermissionError:
logger.debug("Permission denied reading %s", path)
return None


class WindsurfAdapter(FrameworkAdapter):
Copilot is powered by AI and may make mistakes. Always verify output.
Signed-off-by: debu-sinha <debusinha2009@gmail.com>
test_adapters.py: 64 tests covering detect, discover_configs, parse
for Claude Code, Cursor, Windsurf, and VS Code Copilot adapters.
test_discovery.py: 14 tests for discover_agents and helper functions.
test_agent_definitions.py: 84 parametrized tests for agent registry.

Signed-off-by: debu-sinha <debusinha2009@gmail.com>
config = claude_adapter.parse(tmp_path)
assert config.sandbox is not None
assert config.sandbox.enabled is True
assert "api.example.com" in config.sandbox.network_allowed_domains

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High test

The string
api.example.com
may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI about 2 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

Signed-off-by: debu-sinha <debusinha2009@gmail.com>
Signed-off-by: debu-sinha <debusinha2009@gmail.com>
@debu-sinha debu-sinha merged commit 00fc173 into main Apr 1, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-framework skill scanning (Claude Code, Cursor, Windsurf, LangChain) Multi-agent auto-discovery engine

2 participants