Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
1 change: 1 addition & 0 deletions docs/html/cli-reference.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ <h2>User intent to command</h2>
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</pre></div>
</section>
</main>
Expand Down
1 change: 1 addition & 0 deletions docs/html/release-history.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ <h2>0.9 Patch Track</h2>
<tr><td>v0.9.10</td><td>Implemented</td><td>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.</td></tr>
<tr><td>v0.9.11</td><td>Implemented</td><td>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.</td></tr>
<tr><td>v0.9.12</td><td>Implemented</td><td>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.</td></tr>
<tr><td>v0.9.13</td><td>Implemented</td><td>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.</td></tr>
</table>
</section>
<section>
Expand Down
1 change: 1 addition & 0 deletions docs/release-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/invart/benchmarks/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
32 changes: 32 additions & 0 deletions src/invart/benchmarks/releases_v52_v57.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down Expand Up @@ -515,13 +517,43 @@ 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",
"run_claude_reference_adapter_benchmark",
"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",
Expand Down
2 changes: 1 addition & 1 deletion src/invart/commands/parser_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

Expand Down
1 change: 1 addition & 0 deletions src/invart/evaluation/benchmark_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
1 change: 1 addition & 0 deletions src/invart/evaluation/release_candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
)
Expand Down
15 changes: 15 additions & 0 deletions src/invart/evaluation/roadmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
),
)


Expand Down
12 changes: 12 additions & 0 deletions src/invart/surfaces/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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"}:
Expand Down
5 changes: 4 additions & 1 deletion src/invart/surfaces/native_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 .")
Expand Down Expand Up @@ -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 {})
Expand Down
36 changes: 36 additions & 0 deletions tests/test_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
Loading