diff --git a/docs/cli-reference.md b/docs/cli-reference.md
index 4a74ef2..4e10651 100644
--- a/docs/cli-reference.md
+++ b/docs/cli-reference.md
@@ -119,5 +119,6 @@ invart eval benchmark --suite v0.9.9-conformance-contract-v2
invart eval benchmark --suite v0.9.10-opencode-real-adapter
invart eval benchmark --suite v0.9.11-terminal-agent-managed-wrappers
invart eval benchmark --suite v0.9.12-codex-boundary
+invart eval benchmark --suite v0.9.13-ide-bridge-inventory
invart roadmap status --require-full
```
diff --git a/docs/html/cli-reference.html b/docs/html/cli-reference.html
index 9d6960e..2f286df 100644
--- a/docs/html/cli-reference.html
+++ b/docs/html/cli-reference.html
@@ -66,6 +66,7 @@
User intent to command
invart eval benchmark --suite v0.9.10-opencode-real-adapter
invart eval benchmark --suite v0.9.11-terminal-agent-managed-wrappers
invart eval benchmark --suite v0.9.12-codex-boundary
+invart eval benchmark --suite v0.9.13-ide-bridge-inventory
invart roadmap status --require-full
diff --git a/docs/html/release-history.html b/docs/html/release-history.html
index 13a1043..2d22d0a 100644
--- a/docs/html/release-history.html
+++ b/docs/html/release-history.html
@@ -43,6 +43,7 @@ 0.9 Patch Track
| v0.9.10 | Implemented | OpenCode real adapter track: binary-backed managed wrapper run, plugin/MCP config inventory, L5 artifact export, benign autonomy preservation, and managed risk blocking before side effects without treating plugin-only inventory as mediation. |
| v0.9.11 | Implemented | Terminal-agent managed wrappers for Gemini CLI and Aider: binary-backed wrapper runs, Gemini MCP/config inventory, Aider config/repo-context inventory, artifact parity, and low approval-noise checks for benign workflows. |
| v0.9.12 | Implemented | Codex deep adapter boundary: Codex managed-wrapper runs remain Invart-mediated, while Codex-native sandbox, approval, network, and credential-boundary facts are imported as vendor-owned evidence and rejected if inflated into Invart enforcement. |
+ | v0.9.13 | Implemented | IDE extension bridge and inventory track for Cursor, Cline, and Roo: config/MCP/extension discovery is reported as a coverage gap, while explicit native bridge events preserve source metadata and can receive normalized decisions. |
diff --git a/docs/release-history.md b/docs/release-history.md
index a1318df..3f91216 100644
--- a/docs/release-history.md
+++ b/docs/release-history.md
@@ -42,6 +42,7 @@ This page is a reference, not the first-run learning path. Start with [Product O
| v0.9.10 | Implemented | OpenCode real adapter track: binary-backed managed wrapper run, plugin/MCP config inventory, L5 artifact export, benign autonomy preservation, and managed risk blocking before side effects without treating plugin-only inventory as mediation. |
| v0.9.11 | Implemented | Terminal-agent managed wrappers for Gemini CLI and Aider: binary-backed wrapper runs, Gemini MCP/config inventory, Aider config/repo-context inventory, artifact parity, and low approval-noise checks for benign workflows. |
| v0.9.12 | Implemented | Codex deep adapter boundary: Codex managed-wrapper runs remain Invart-mediated, while Codex-native sandbox, approval, network, and credential-boundary facts are imported as vendor-owned evidence and rejected if inflated into Invart enforcement. |
+| v0.9.13 | Implemented | IDE extension bridge and inventory track for Cursor, Cline, and Roo: config/MCP/extension discovery is reported as a coverage gap, while explicit native bridge events preserve source metadata and can receive normalized decisions. |
## Internal History
diff --git a/src/invart/benchmarks/registry.py b/src/invart/benchmarks/registry.py
index e0d9be3..d556a45 100644
--- a/src/invart/benchmarks/registry.py
+++ b/src/invart/benchmarks/registry.py
@@ -62,6 +62,7 @@
run_codex_boundary_benchmark,
run_conformance_contract_v2_benchmark,
run_evidence_workspace_gate_benchmark,
+ run_ide_bridge_inventory_benchmark,
run_layer_runtime_workflow_benchmark,
run_opencode_real_adapter_benchmark,
run_priority_agent_tracks_benchmark,
@@ -131,6 +132,7 @@ def benchmark_runner_registry() -> dict[str, BenchmarkRunner]:
"v0.9.10-opencode-real-adapter": run_opencode_real_adapter_benchmark,
"v0.9.11-terminal-agent-managed-wrappers": run_terminal_agent_managed_wrappers_benchmark,
"v0.9.12-codex-boundary": run_codex_boundary_benchmark,
+ "v0.9.13-ide-bridge-inventory": run_ide_bridge_inventory_benchmark,
"progressive-external-validation": run_progressive_external_validation_benchmark,
"real-world-agent-risk-demo": run_real_world_risk_benchmark,
"containerized-risk-demo": run_container_risk_demo_benchmark,
diff --git a/src/invart/benchmarks/releases_v52_v57.py b/src/invart/benchmarks/releases_v52_v57.py
index 905ee22..7ccf844 100644
--- a/src/invart/benchmarks/releases_v52_v57.py
+++ b/src/invart/benchmarks/releases_v52_v57.py
@@ -19,6 +19,8 @@
from invart.surfaces.live_adapter import run_live_agent_adapter
from invart.surfaces.adapter_profiles import adapter_track_matrix, list_adapter_profiles, validate_adapter_profile_truthfulness
from invart.surfaces.vendor_evidence import import_vendor_native_evidence, validate_vendor_claim_boundary
+from invart.surfaces.native import native_capability_matrix, unmanaged_agent_inventory
+from invart.surfaces.native_bridge import bridge_conformance_matrix, normalize_native_event
def run_agent_adapter_contract_benchmark() -> dict[str, object]:
@@ -515,6 +517,35 @@ def run_codex_boundary_benchmark() -> dict[str, object]:
)
+def run_ide_bridge_inventory_benchmark() -> dict[str, object]:
+ with tempfile.TemporaryDirectory(prefix="invart_v0913_") as tmp:
+ root = Path(tmp)
+ (root / ".cursor").mkdir()
+ (root / ".cursor" / "mcp.json").write_text('{"mcpServers":{"fs":{}}}\n', encoding="utf-8")
+ (root / ".cline").mkdir()
+ (root / ".cline" / "settings.json").write_text('{"tools":["shell"]}\n', encoding="utf-8")
+ (root / ".roo").mkdir()
+ (root / ".roo" / "mcp.json").write_text('{"mcpServers":{"repo":{}}}\n', encoding="utf-8")
+ matrix = native_capability_matrix(root)
+ unmanaged = unmanaged_agent_inventory(root)
+ by_agent = {item["agent"]: item for item in matrix["agents"]}
+ bridge = bridge_conformance_matrix()
+ action = normalize_native_event("cursor", {"tool": "shell", "arguments": {"command": "echo ok"}, "session_id": "bench"})
+ checks = {
+ "cursor_discovery_not_mediation": all(surface["coverage_state"] != "mediated" for surface in by_agent["cursor"]["surfaces"].values() if surface["source_evidence"]),
+ "cline_discovery_not_mediation": all(surface["coverage_state"] != "mediated" for surface in by_agent["cline"]["surfaces"].values() if surface["source_evidence"]),
+ "roo_discovery_not_mediation": all(surface["coverage_state"] != "mediated" for surface in by_agent["roo-code"]["surfaces"].values() if surface["source_evidence"]),
+ "unmanaged_gaps_reported": {"cursor", "cline", "roo-code"}.issubset({item["agent"] for item in unmanaged["findings"]}),
+ "bridge_conformance_includes_ide": bridge.get("status") == "pass" and {"cursor", "cline", "roo-code"}.issubset({item["agent"] for item in bridge["cases"]}),
+ "imported_event_has_native_source": action.adapter == "native_hook:cursor" and action.metadata["coverage_layer"] == "native_hook",
+ }
+ return _suite_result(
+ "v0.9.13-ide-bridge-inventory",
+ checks,
+ artifacts={},
+ )
+
+
__all__ = [
"run_agent_adapter_contract_benchmark",
"run_claude_full_live_adapter_benchmark",
@@ -522,6 +553,7 @@ def run_codex_boundary_benchmark() -> dict[str, object]:
"run_conformance_contract_v2_benchmark",
"run_codex_boundary_benchmark",
"run_evidence_workspace_gate_benchmark",
+ "run_ide_bridge_inventory_benchmark",
"run_opencode_real_adapter_benchmark",
"run_terminal_agent_managed_wrappers_benchmark",
"run_priority_agent_tracks_benchmark",
diff --git a/src/invart/commands/parser_integrations.py b/src/invart/commands/parser_integrations.py
index 32b35d7..0b0c279 100644
--- a/src/invart/commands/parser_integrations.py
+++ b/src/invart/commands/parser_integrations.py
@@ -195,7 +195,7 @@ def register_integration_commands(subparsers: argparse._SubParsersAction[argpars
bridge.set_defaults(handler=handle_bridge)
bridge_sub = bridge.add_subparsers(dest="bridge_command", required=True)
bridge_native = bridge_sub.add_parser("native", help="Normalize a native hook payload and return a native response.")
- bridge_native.add_argument("--agent", choices=("claude-code", "codex", "opencode", "generic"), required=True)
+ bridge_native.add_argument("--agent", choices=("claude-code", "codex", "opencode", "cursor", "cline", "roo-code", "generic"), required=True)
bridge_native.add_argument("--event", required=True)
bridge_conformance = bridge_sub.add_parser("conformance", help="Run native bridge response-shape conformance checks.")
diff --git a/src/invart/evaluation/benchmark_registry.py b/src/invart/evaluation/benchmark_registry.py
index e4e000f..a94fbd4 100644
--- a/src/invart/evaluation/benchmark_registry.py
+++ b/src/invart/evaluation/benchmark_registry.py
@@ -41,6 +41,7 @@
{"suite": "v0.9.10-opencode-real-adapter", "version": "v0.9.10", "category": "agent-adapter", "optional_heavy": False, "claim_scope": "local_opencode_managed_wrapper", "evidence_level": "binary_backed_fixture_with_plugin_inventory"},
{"suite": "v0.9.11-terminal-agent-managed-wrappers", "version": "v0.9.11", "category": "compatibility", "optional_heavy": False, "claim_scope": "local_terminal_agent_wrappers", "evidence_level": "binary_backed_fixture_with_config_inventory"},
{"suite": "v0.9.12-codex-boundary", "version": "v0.9.12", "category": "agent-adapter", "optional_heavy": False, "claim_scope": "codex_vendor_boundary", "evidence_level": "vendor_native_import_plus_managed_wrapper_fixture"},
+ {"suite": "v0.9.13-ide-bridge-inventory", "version": "v0.9.13", "category": "coverage", "optional_heavy": False, "claim_scope": "ide_bridge_inventory", "evidence_level": "local_discovery_fixture_plus_bridge_event"},
{"suite": "progressive-external-validation", "version": "pre-release", "category": "external-validation", "optional_heavy": False, "claim_scope": "progressive_external_validation", "evidence_level": "external_progressive_sample"},
{"suite": "real-world-agent-risk-demo", "version": "pre-release", "category": "demo", "optional_heavy": False, "claim_scope": "public_source_mapping", "evidence_level": "public_source_seed_plus_local_demo"},
{"suite": "containerized-risk-demo", "version": "pre-release", "category": "demo", "optional_heavy": False, "claim_scope": "containerized_local_demo", "evidence_level": "per_case_container_artifact_bundle"},
diff --git a/src/invart/evaluation/release_candidate.py b/src/invart/evaluation/release_candidate.py
index 21617d6..f06c230 100644
--- a/src/invart/evaluation/release_candidate.py
+++ b/src/invart/evaluation/release_candidate.py
@@ -58,6 +58,7 @@
"v0.9.10-opencode-real-adapter",
"v0.9.11-terminal-agent-managed-wrappers",
"v0.9.12-codex-boundary",
+ "v0.9.13-ide-bridge-inventory",
"progressive-external-validation",
"pre-v1-control-plane",
)
diff --git a/src/invart/evaluation/roadmap.py b/src/invart/evaluation/roadmap.py
index 1a0da9a..a37fec4 100644
--- a/src/invart/evaluation/roadmap.py
+++ b/src/invart/evaluation/roadmap.py
@@ -917,6 +917,21 @@ def internal_docs_for_capability(docs: list[str]) -> list[str]:
external_validation="not_run_optional",
next_step="Add IDE extension bridge and inventory tracks for Cursor, Cline, and Roo without overclaiming discovery-only evidence.",
),
+ RoadmapCapability(
+ version="v0.9.13",
+ capability_id="ide_extension_bridge_inventory",
+ title="IDE extension bridge and inventory track",
+ target="Inventory Cursor, Cline, and Roo IDE agent surfaces and support normalized native bridge events without letting discovery-only evidence satisfy runtime mediation claims.",
+ status="implemented",
+ implementation=["src/invart/surfaces/native.py", "src/invart/surfaces/native_bridge.py", "src/invart/commands/parser_integrations.py", "src/invart/benchmarks/releases_v52_v57.py"],
+ tests=["test_v0913_ide_inventory_is_discovery_not_mediation", "test_v0913_ide_bridge_event_preserves_source_and_cli", "v0.9.13-ide-bridge-inventory"],
+ docs=["docs/cli-reference.md", "docs/html/cli-reference.html", "docs/release-history.md", "docs/html/release-history.html", "docs/roadmap.md", "docs/html/roadmap.html"],
+ product_boundaries=["Local discovery fixtures validate IDE inventory behavior; installed extension validation remains optional.", "Cursor, Cline, and Roo config discovery is coverage-gap evidence and not mediation.", "Only explicit bridge events entering Invart can be treated as mediated native-event evidence."],
+ claim_scope="ide_bridge_inventory",
+ evidence_level="local_discovery_fixture_plus_bridge_event",
+ external_validation="not_run_optional",
+ next_step="Add gateway and server-agent evidence imports for OpenClaw and Hermes while preserving vendor-owned boundaries.",
+ ),
)
diff --git a/src/invart/surfaces/native.py b/src/invart/surfaces/native.py
index c6be820..6d41ced 100644
--- a/src/invart/surfaces/native.py
+++ b/src/invart/surfaces/native.py
@@ -29,6 +29,16 @@
"mcp": [".cursor/mcp.json"],
"hooks": [".cursor/hooks"],
},
+ "cline": {
+ "settings": [".cline/settings.json", ".vscode/settings.json"],
+ "mcp": [".cline/mcp.json", ".vscode/mcp.json"],
+ "extension": [".cline", ".vscode/extensions.json"],
+ },
+ "roo-code": {
+ "settings": [".roo/settings.json", ".roomodes", ".vscode/settings.json"],
+ "mcp": [".roo/mcp.json", ".vscode/mcp.json"],
+ "extension": [".roo", ".vscode/extensions.json"],
+ },
"opencode": {
"plugins": ["opencode.json", ".opencode/plugin"],
"mcp": ["opencode.json", ".opencode/mcp.json"],
@@ -280,6 +290,8 @@ def _surface_coverage_state(agent: str, surface_name: str, matches: list[dict[st
return "not_covered"
if agent in {"hermes", "openclaw"}:
return "vendor_owned"
+ if agent in {"cursor", "cline", "roo-code"}:
+ return "discovered"
if surface_name in {"hooks", "plugins", "extensions", "mcp"}:
return "mediated"
if surface_name in {"sandbox"}:
diff --git a/src/invart/surfaces/native_bridge.py b/src/invart/surfaces/native_bridge.py
index e525268..77c664e 100644
--- a/src/invart/surfaces/native_bridge.py
+++ b/src/invart/surfaces/native_bridge.py
@@ -50,7 +50,7 @@ def render_native_response(agent: str, decision: dict[str, Any]) -> dict[str, An
def bridge_conformance_matrix() -> dict[str, Any]:
- agents = ("claude-code", "codex", "opencode", "generic")
+ agents = ("claude-code", "codex", "opencode", "cursor", "cline", "roo-code", "generic")
cases = []
for agent in agents:
shell_payload = _fixture_payload(agent, "rm -rf .")
@@ -87,6 +87,9 @@ def _extract_tool_event(agent: str, payload: dict[str, Any]) -> tuple[str, str,
elif agent == "opencode":
tool = str(payload.get("tool") or payload.get("tool_name") or "unknown")
parameters = dict(payload.get("input") or payload.get("arguments") or {})
+ elif agent in {"cursor", "cline", "roo-code"}:
+ tool = str(payload.get("tool") or payload.get("tool_name") or payload.get("command_name") or "unknown")
+ parameters = dict(payload.get("arguments") or payload.get("input") or payload.get("tool_input") or {})
else:
tool = str(payload.get("tool") or payload.get("tool_name") or "unknown")
parameters = dict(payload.get("arguments") or payload.get("tool_input") or {})
diff --git a/tests/test_integrations.py b/tests/test_integrations.py
index 4979bc6..f0ada22 100644
--- a/tests/test_integrations.py
+++ b/tests/test_integrations.py
@@ -1250,6 +1250,42 @@ def test_v0912_codex_managed_wrapper_remains_invart_mediated(tmp_path: Path) ->
assert main(["eval", "benchmark", "--suite", "v0.9.12-codex-boundary"]) == 0
+def test_v0913_ide_inventory_is_discovery_not_mediation(tmp_path: Path) -> None:
+ from invart.surfaces.native import native_capability_matrix, unmanaged_agent_inventory
+
+ (tmp_path / ".cursor").mkdir()
+ (tmp_path / ".cursor" / "mcp.json").write_text(json.dumps({"mcpServers": {"fs": {}}}), encoding="utf-8")
+ (tmp_path / ".cline").mkdir()
+ (tmp_path / ".cline" / "settings.json").write_text(json.dumps({"tools": ["shell"]}), encoding="utf-8")
+ (tmp_path / ".roo").mkdir()
+ (tmp_path / ".roo" / "mcp.json").write_text(json.dumps({"mcpServers": {"repo": {}}}), encoding="utf-8")
+ matrix = native_capability_matrix(tmp_path)
+ by_agent = {item["agent"]: item for item in matrix["agents"]}
+ for agent in ("cursor", "cline", "roo-code"):
+ discovered = [surface for surface in by_agent[agent]["surfaces"].values() if surface["source_evidence"]]
+ assert discovered
+ assert all(surface["coverage_state"] == "discovered" for surface in discovered)
+ assert all(surface["enforcement_claim"] == "not_enforced" for surface in discovered)
+ unmanaged = unmanaged_agent_inventory(tmp_path)
+ assert {"cursor", "cline", "roo-code"}.issubset({item["agent"] for item in unmanaged["findings"]})
+
+
+def test_v0913_ide_bridge_event_preserves_source_and_cli(tmp_path: Path) -> None:
+ payload = {"tool": "shell", "arguments": {"command": "echo ok"}, "session_id": "ide-session"}
+ for agent in ("cursor", "cline", "roo-code"):
+ action = normalize_native_event(agent, payload)
+ assert action.adapter == f"native_hook:{agent}"
+ assert action.metadata["vendor_agent"] == agent
+ assert action.metadata["coverage_layer"] == "native_hook"
+ response = render_native_response(agent, {"effect": "allow", "reason": "ok"})
+ assert response["allow"] is True
+ event_path = tmp_path / "cursor-event.json"
+ event_path.write_text(json.dumps(payload), encoding="utf-8")
+ assert main(["bridge", "native", "--agent", "cursor", "--event", event_path.read_text(encoding="utf-8")]) == 0
+ assert main(["bridge", "conformance"]) == 0
+ assert main(["eval", "benchmark", "--suite", "v0.9.13-ide-bridge-inventory"]) == 0
+
+
def test_v09_swe_bench_lite_runner_skips_cleanly_without_dependencies(tmp_path: Path) -> None:
out = tmp_path / "swebench-report.json"
assert main([