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.10ImplementedOpenCode 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.11ImplementedTerminal-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.12ImplementedCodex 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.13ImplementedIDE 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([