From 7e62afb52df8171ca8555b196aa6a3b58c67ce8b Mon Sep 17 00:00:00 2001 From: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:13:36 +0000 Subject: [PATCH 1/2] feat(vendors): record whether each agent loads Claude Code plugins (CC-2) A new vendor field, loads_claude_plugins, transcribes each agent's row of org-plan discovery/2026-10-17-claude-plugin-compat.md: loads, how (install route), hooks, payload, deny_reply, notes, source. Unknown stays unknown, partial stays partial. Grok alone carries the 2026-10-17 witness (guard and gate refused; org-plan witness/results/witness-grok-claude-plugin-2026-10-17.md at 25b9d9d). Each entry travels with an evidence record. The data lives in tools/recount/claude_plugins.json and is folded in by tools/recount/sourced.py; the vendor JSON is regenerated with tools/recount_vendor_config.py --write. Read through agentseam.vendor_config.loads_claude_plugins(agent). No adapter reads it. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com> --- src/agentseam/data/vendors/antigravity.json | 14 ++ src/agentseam/data/vendors/claude_code.json | 14 ++ src/agentseam/data/vendors/cline.json | 13 ++ src/agentseam/data/vendors/codex_cli.json | 14 ++ src/agentseam/data/vendors/cursor.json | 14 ++ src/agentseam/data/vendors/devin.json | 14 ++ src/agentseam/data/vendors/factory_droid.json | 14 ++ src/agentseam/data/vendors/gemini_cli.json | 14 ++ src/agentseam/data/vendors/goose.json | 14 ++ src/agentseam/data/vendors/grok.json | 15 ++ src/agentseam/data/vendors/junie.json | 14 ++ src/agentseam/data/vendors/kimi_code.json | 14 ++ src/agentseam/data/vendors/mistral_vibe.json | 14 ++ src/agentseam/data/vendors/openhands.json | 14 ++ src/agentseam/data/vendors/schema.json | 17 ++ src/agentseam/data/vendors/tabnine.json | 14 ++ .../data/vendors/vscode_copilot.json | 14 ++ src/agentseam/data/vendors/windsurf.json | 14 ++ src/agentseam/vendor_config.py | 8 +- tests/test_claude_plugins.py | 90 ++++++++ tools/recount/claude_plugins.json | 206 ++++++++++++++++++ tools/recount/sourced.py | 17 ++ 22 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 tests/test_claude_plugins.py create mode 100644 tools/recount/claude_plugins.json diff --git a/src/agentseam/data/vendors/antigravity.json b/src/agentseam/data/vendors/antigravity.json index 4548899..8b4e6e9 100644 --- a/src/agentseam/data/vendors/antigravity.json +++ b/src/agentseam/data/vendors/antigravity.json @@ -38,6 +38,11 @@ "date": "2026-10-17", "test": "tests/test_adapter_antigravity.py::test_hook_config_lists_stop_handlers_bare_as_documented" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-08-26", @@ -92,6 +97,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "no", + "hooks": "n/a", + "how": "n/a; needs a root plugin.json in its own schema (additionalProperties: false)", + "loads": "no", + "notes": "own hooks reply with a top-level decision", + "payload": "own (camelCase toolCall)", + "source": "https://antigravity.google/docs/plugins (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": {}, "verdicts": { diff --git a/src/agentseam/data/vendors/claude_code.json b/src/agentseam/data/vendors/claude_code.json index 8e3d4fe..c27e7c7 100644 --- a/src/agentseam/data/vendors/claude_code.json +++ b/src/agentseam/data/vendors/claude_code.json @@ -90,6 +90,11 @@ "date": "2026-10-13", "test": "tests/test_install_watch.py::test_file_changed_is_not_wired_without_a_watch_list" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "repo_root_token": { "basis": "vendor-docs", "date": "2026-09-01", @@ -152,6 +157,15 @@ ], "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "yes", + "hooks": "yes", + "how": "native: marketplace install into ~/.claude/plugins/cache////; --plugin-dir loads in place", + "loads": "yes", + "notes": "hooks run when the plugin is enabled; HSO deny and exit 2 both block", + "payload": "Claude Code", + "source": "https://code.claude.com/docs/en/plugins/loading.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "repo_root_token": "${CLAUDE_PROJECT_DIR}", "tools": { diff --git a/src/agentseam/data/vendors/cline.json b/src/agentseam/data/vendors/cline.json index b6e496a..981867b 100644 --- a/src/agentseam/data/vendors/cline.json +++ b/src/agentseam/data/vendors/cline.json @@ -64,6 +64,11 @@ "date": "2026-10-11", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-10-11", @@ -131,6 +136,14 @@ "matcher": false, "wrapper": "hook_dir" }, + "loads_claude_plugins": { + "deny_reply": "n/a", + "hooks": "n/a", + "how": "n/a; two package lanes only (Agent Plugins root plugin.json, Cline JS/TS modules)", + "loads": "no", + "payload": "n/a", + "source": "cline/cline@a42df8c sdk/packages/shared/src/storage/paths.ts (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": true, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/codex_cli.json b/src/agentseam/data/vendors/codex_cli.json index 5c46bca..e2f91ce 100644 --- a/src/agentseam/data/vendors/codex_cli.json +++ b/src/agentseam/data/vendors/codex_cli.json @@ -65,6 +65,11 @@ "date": "2026-08-28", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "live-run-partial", "date": "2026-08-28", @@ -119,6 +124,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "yes", + "hooks": "yes", + "how": "manifest lookup .codex-plugin/, then .claude-plugin/plugin.json; marketplace .claude-plugin/marketplace.json; codex plugin marketplace add then codex plugin add P@M; no bare-folder install", + "loads": "yes", + "notes": "hooks from the manifest's hooks, else hooks/hooks.json; per-hook trust state unmeasured; an HSO deny needs a non-empty permissionDecisionReason and exit 2 needs non-empty stderr; Sync handlers only", + "payload": "Claude Code snake_case, Codex tool names", + "source": "openai/codex@ce0323f codex-rs/exec-server-protocol/src/protocol.rs, codex-rs/core-plugins/src/loader.rs (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": true, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/cursor.json b/src/agentseam/data/vendors/cursor.json index 0a93727..7dc82c5 100644 --- a/src/agentseam/data/vendors/cursor.json +++ b/src/agentseam/data/vendors/cursor.json @@ -57,6 +57,11 @@ "date": "2026-09-23", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "live-run-partial", "date": "2026-09-23", @@ -110,6 +115,15 @@ "matcher": false, "wrapper": "cursor" }, + "loads_claude_plugins": { + "deny_reply": "unknown", + "hooks": "unknown", + "how": "n/a for the documented formats (.cursor-plugin/plugin.json and a root plugin.json); unknown via the Third-Party Plugins toggle", + "loads": "unknown", + "notes": "recorded no for the documented plugin formats and unknown via the Third-Party Plugins toggle, so unknown overall; Claude Code settings hooks (not plugins) run by default, event and tool names mapped (Bash to Shell), HSO and exit 2 honoured", + "payload": "unknown", + "source": "https://cursor.com/docs/reference/plugins, https://cursor.com/docs/reference/third-party-hooks (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": { "write": [ diff --git a/src/agentseam/data/vendors/devin.json b/src/agentseam/data/vendors/devin.json index 39de889..6cdd4d8 100644 --- a/src/agentseam/data/vendors/devin.json +++ b/src/agentseam/data/vendors/devin.json @@ -62,6 +62,11 @@ "date": "2026-08-26", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-08-26", @@ -108,6 +113,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "unknown", + "how": "devin plugins install --local ./dir or a repo (sign-in needed); falls back to .claude-plugin/plugin.json when no .devin-plugin/plugin.json", + "loads": "yes", + "notes": "the docs promise only a root hooks.json; plugin hooks are best effort and fail open; exit 2 and a top-level decision block, HSO permissionDecision undocumented", + "payload": "Claude-shaped snake_case, Devin tool names (exec)", + "source": "https://docs.devin.ai/cli/extensibility/plugins/overview (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": {}, "verdicts": { diff --git a/src/agentseam/data/vendors/factory_droid.json b/src/agentseam/data/vendors/factory_droid.json index 8a2d680..dac2873 100644 --- a/src/agentseam/data/vendors/factory_droid.json +++ b/src/agentseam/data/vendors/factory_droid.json @@ -60,6 +60,11 @@ "date": "2026-10-11", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-10-11", @@ -106,6 +111,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "yes", + "hooks": "yes", + "how": "via a marketplace only: droid plugin marketplace add then droid plugin install p@m --scope user|project; .claude-plugin/ translated to .factory-plugin/ on cache copy; a bare plugin dir is rejected", + "loads": "yes", + "notes": "hooks run when enabled; a Claude Bash matcher may not match Droid's tool names (unmeasured); exit 2 and HSO deny both block", + "payload": "Claude Code snake_case, Droid tool names (Execute, Create)", + "source": "https://docs.factory.com/harness/plugins.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/gemini_cli.json b/src/agentseam/data/vendors/gemini_cli.json index c0566ab..8f56f38 100644 --- a/src/agentseam/data/vendors/gemini_cli.json +++ b/src/agentseam/data/vendors/gemini_cli.json @@ -64,6 +64,11 @@ "date": "2026-08-28", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-source", "date": "2026-08-28", @@ -113,6 +118,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "n/a", + "how": "n/a; extensions need gemini-extension.json", + "loads": "no", + "notes": "own hooks: exit 2 and a top-level decision; a one-shot gemini hooks migrate --from-claude exists", + "payload": "own events (BeforeTool), snake_case keys", + "source": "google-gemini/gemini-cli@ed96fae docs/extensions/reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/goose.json b/src/agentseam/data/vendors/goose.json index bfc5e2a..6fb619b 100644 --- a/src/agentseam/data/vendors/goose.json +++ b/src/agentseam/data/vendors/goose.json @@ -55,6 +55,11 @@ "date": "2026-10-11", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-10-11", @@ -101,6 +106,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "yes", + "how": "place the folder in .agents/plugins// or ~/.agents/plugins// (loads by layout; .claude-plugin/plugin.json is not a goose manifest); no ~/.claude/plugins, no Claude marketplace", + "loads": "partial", + "notes": "hooks/hooks.json runs; exit 2 or {\"decision\":\"block\"} blocks; HSO is not parsed", + "payload": "own: key event, not hook_event_name; goose tool names (developer__shell)", + "source": "block/goose@bfcc6ee crates/goose/src/plugins/formats/open_plugins.rs, crates/goose/src/hooks/mod.rs (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/grok.json b/src/agentseam/data/vendors/grok.json index ac34bf8..95a6ec2 100644 --- a/src/agentseam/data/vendors/grok.json +++ b/src/agentseam/data/vendors/grok.json @@ -76,6 +76,11 @@ "date": "2026-10-13", "test": "tests/test_adapter_grok.py::test_tool_failure_also_wires_permission_denied" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_grok_carries_the_witness_and_no_other_agent_does" + }, "tools": { "basis": "vendor-docs", "date": "2026-08-26", @@ -127,6 +132,16 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "yes", + "hooks": "yes", + "how": "scans ~/.claude/plugins/*/ (user, auto-trusted) and .claude/plugins/*/ (project, needs trust), plus installed_plugins.json; grok plugin install --trust; manifest fallback .claude-plugin/plugin.json; off until enabled", + "loads": "yes", + "notes": "hooks run when enabled and trusted; HSO deny and exit 2 block, other failures fail open", + "payload": "grok's camelCase envelope with additive Claude snake_case aliases; camelCase authoritative; grok tool names (run_terminal_command)", + "source": "xai-org/grok-build@2bdd1d6 crates/codegen/xai-grok-agent/src/plugins/discovery.rs, docs/user-guide/10-hooks.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)", + "witnessed": "guard and gate refusal: Grok Build 1.0.62 on Windows 11, 2026-10-17; guard and gate refused with the plugin installed by Claude Code, gate refused with it installed by grok plugin install --trust (org-plan witness/results/witness-grok-claude-plugin-2026-10-17.md at 25b9d9d)" + }, "needs_trust": true, "tools": {}, "verdicts": { diff --git a/src/agentseam/data/vendors/junie.json b/src/agentseam/data/vendors/junie.json index eed5e78..b892ee9 100644 --- a/src/agentseam/data/vendors/junie.json +++ b/src/agentseam/data/vendors/junie.json @@ -51,6 +51,11 @@ "date": "2026-08-26", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-08-26", @@ -99,6 +104,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "yes", + "how": "through a Claude-format marketplace: /extensions marketplace add with a .claude-plugin/marketplace.json index; interactive install; cached in ~/.junie/extensions/", + "loads": "yes", + "notes": "per-extension manifest undocumented; hooks are Early Access; exit 2 and a top-level decision block, HSO not documented as blocking", + "payload": "Claude Code (hook_event_name, tool_name: \"Bash\")", + "source": "https://junie.jetbrains.com/docs/junie-cli-extensions.html (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": {}, "verdicts": { diff --git a/src/agentseam/data/vendors/kimi_code.json b/src/agentseam/data/vendors/kimi_code.json index 0b04a64..5df9c99 100644 --- a/src/agentseam/data/vendors/kimi_code.json +++ b/src/agentseam/data/vendors/kimi_code.json @@ -63,6 +63,11 @@ "date": "2026-08-26", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-08-26", @@ -110,6 +115,15 @@ "matcher": true, "wrapper": "flat_list" }, + "loads_claude_plugins": { + "deny_reply": "n/a", + "hooks": "n/a", + "how": "n/a; manifest is kimi.plugin.json or .kimi-plugin/plugin.json only", + "loads": "no", + "notes": "own hooks are Claude-shaped snake_case and honour HSO and exit 2", + "payload": "n/a", + "source": "https://www.kimi.com/code/docs/en/kimi-code-cli/customization/plugins.html (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": {}, "verdicts": { diff --git a/src/agentseam/data/vendors/mistral_vibe.json b/src/agentseam/data/vendors/mistral_vibe.json index 389c24c..6a78dea 100644 --- a/src/agentseam/data/vendors/mistral_vibe.json +++ b/src/agentseam/data/vendors/mistral_vibe.json @@ -46,6 +46,11 @@ "date": "2026-10-11", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-10-11", @@ -102,6 +107,15 @@ "rule_name_prefix": "agentseam-", "wrapper": "flat_list" }, + "loads_claude_plugins": { + "deny_reply": "unknown", + "hooks": "unknown", + "how": "detected and adapted: auto-discovery under ~/.vibe/plugins/ or .vibe/plugins/ (trusted folder); no install command", + "loads": "yes", + "notes": "the adapter reads hooks/hooks.json into HookProtocol.CLAUDE_CODE definitions, but no executor for them was found, and the vendor's two skill docs contradict each other", + "payload": "unknown", + "source": "mistralai/mistral-vibe@4ae5c59 vibe/core/plugins/_claude.py (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": true, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/openhands.json b/src/agentseam/data/vendors/openhands.json index 821c721..658bd6f 100644 --- a/src/agentseam/data/vendors/openhands.json +++ b/src/agentseam/data/vendors/openhands.json @@ -49,6 +49,11 @@ "date": "2026-10-11", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-10-11", @@ -92,6 +97,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "yes", + "how": "openhands --plugin /path; .claude-plugin/ is a supported metadata dir; marketplaces from .claude-plugin/marketplace.json", + "loads": "yes", + "notes": "hooks/hooks.json runs; exit 2 and a top-level decision: deny block; HSO is not parsed", + "payload": "mixed: snake_case tool_name/tool_input, but event key event_type (not hook_event_name); OpenHands tool names", + "source": "https://docs.openhands.dev/overview/plugins; OpenHands/software-agent-sdk@5966c78 openhands-sdk/openhands/sdk/hooks/types.py (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": { "shell": [ diff --git a/src/agentseam/data/vendors/schema.json b/src/agentseam/data/vendors/schema.json index 56ffc9d..a379da8 100644 --- a/src/agentseam/data/vendors/schema.json +++ b/src/agentseam/data/vendors/schema.json @@ -33,6 +33,22 @@ "$ref": "#/$defs/stringList", "description": "repo-relative hook configs of OTHER agents that this agent also loads and runs (grok reads Claude Code's and Cursor's). Opt-in and primary-sourced; travels with an evidence.also_reads record. `doctor` names any of them carrying agentseam entries, which an uninstall of this agent leaves in place." }, + "loads_claude_plugins": { + "type": "object", + "additionalProperties": false, + "required": ["loads", "how", "hooks", "payload", "deny_reply", "source"], + "description": "whether this agent loads a Claude Code plugin (.claude-plugin/plugin.json, hooks/hooks.json), one row of org-plan discovery/2026-10-17-claude-plugin-compat.md transcribed: unknown stays unknown, partial stays partial. A fact, not a capability: nothing in the runtime reads it. Travels with an evidence.loads_claude_plugins record.", + "properties": { + "loads": {"enum": ["yes", "partial", "no", "unknown"]}, + "how": {"type": "string", "minLength": 1, "description": "the install route, as recorded"}, + "hooks": {"enum": ["yes", "no", "unknown", "n/a"], "description": "whether the plugin's hooks/hooks.json runs"}, + "payload": {"type": "string", "minLength": 1, "description": "the dialect those hooks receive on stdin, as recorded"}, + "deny_reply": {"enum": ["yes", "partial", "no", "unknown", "n/a"], "description": "whether Claude's deny (hookSpecificOutput.permissionDecision deny, or exit 2) is honoured"}, + "notes": {"type": "string", "minLength": 1, "description": "the row's qualifiers on the cells above"}, + "source": {"type": "string", "minLength": 1, "description": "the vendor docs or source the row rests on"}, + "witnessed": {"type": "string", "minLength": 1, "description": "a witness run of the Claude Code plugin in this agent: what it showed and the result file it is recorded in. Absent means never witnessed."} + } + }, "legacy_config_paths": { "$ref": "#/$defs/stringList", "description": "repo-relative hook configs this agent read before config_path and still reads as a fallback (windsurf's .windsurf/hooks.json, read only while .devin/hooks.json defines no hooks). Opt-in and primary-sourced; travels with an evidence.legacy_config_paths record. `install` strips agentseam entries from them and refuses when one holds the user's own hooks that config_path would switch off; `uninstall` cleans them." @@ -307,6 +323,7 @@ "config_path": {"$ref": "#/$defs/evidenceRecord"}, "hook_entry": {"$ref": "#/$defs/evidenceRecord"}, "also_reads": {"$ref": "#/$defs/evidenceRecord"}, + "loads_claude_plugins": {"$ref": "#/$defs/evidenceRecord"}, "legacy_config_paths": {"$ref": "#/$defs/evidenceRecord"}, "repo_root_token": {"$ref": "#/$defs/evidenceRecord"}, "trust_hint": {"$ref": "#/$defs/evidenceRecord"} diff --git a/src/agentseam/data/vendors/tabnine.json b/src/agentseam/data/vendors/tabnine.json index b55410c..70ebfa1 100644 --- a/src/agentseam/data/vendors/tabnine.json +++ b/src/agentseam/data/vendors/tabnine.json @@ -53,6 +53,11 @@ "date": "2026-08-26", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-08-26", @@ -101,6 +106,15 @@ "matcher": true, "wrapper": "hooks_map" }, + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "unknown", + "how": "tabnine extensions install translates if necessary; Claude never named", + "loads": "unknown", + "notes": "own hooks: exit 2 and decision: deny; the CLI is in maintenance mode until 2026-12-31", + "payload": "own event names (BeforeTool)", + "source": "https://docs.tabnine.com/main/getting-started/tabnine-cli/features/extensions (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": {}, "verdicts": { diff --git a/src/agentseam/data/vendors/vscode_copilot.json b/src/agentseam/data/vendors/vscode_copilot.json index 436b0f4..fd3c7f2 100644 --- a/src/agentseam/data/vendors/vscode_copilot.json +++ b/src/agentseam/data/vendors/vscode_copilot.json @@ -64,6 +64,11 @@ "date": "2026-09-28", "test": "tests/test_golden_fixtures.py::test_hook_config_matches_the_frozen_fixture_on_both_matcher_paths" }, + "loads_claude_plugins": { + "basis": "vendor-source", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "vendor-docs", "date": "2026-09-01", @@ -138,6 +143,15 @@ "matcher": false, "wrapper": "flat_entries" }, + "loads_claude_plugins": { + "deny_reply": "yes", + "hooks": "yes", + "how": "Claude-format plugin by manifest location, from a marketplace (.claude-plugin/marketplace.json, anthropics/claude-code accepted) or chat.pluginLocations; needs chat.plugins.enabled", + "loads": "yes", + "notes": "the Local harness ignores matchers, so every nested command for the event runs; HSO deny and exit 2 block", + "payload": "Claude-shaped snake_case, but VS Code's own Local tool names", + "source": "microsoft/vscode-docs@ef0ca127 docs/agents/reference/agent-plugin-reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": { "memory": [ diff --git a/src/agentseam/data/vendors/windsurf.json b/src/agentseam/data/vendors/windsurf.json index 316c315..ab14012 100644 --- a/src/agentseam/data/vendors/windsurf.json +++ b/src/agentseam/data/vendors/windsurf.json @@ -53,6 +53,11 @@ "date": "2026-10-13", "test": "tests/test_install_legacy.py::test_windsurf_wires_devin_hooks_json_and_cleans_the_legacy_file" }, + "loads_claude_plugins": { + "basis": "vendor-docs", + "date": "2026-10-17", + "test": "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" + }, "tools": { "basis": "third-party-install", "date": "2026-08-26", @@ -101,6 +106,15 @@ "legacy_config_paths": [ ".windsurf/hooks.json" ], + "loads_claude_plugins": { + "deny_reply": "partial", + "hooks": "unknown", + "how": "no plugin system documented", + "loads": "unknown", + "notes": "own hooks: exit 2 only", + "payload": "own (agent_action_name, tool_info)", + "source": "https://docs.devin.ai/desktop/cascade/hooks (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + }, "needs_trust": false, "tools": {}, "verdicts": { diff --git a/src/agentseam/vendor_config.py b/src/agentseam/vendor_config.py index 491c611..a2a2017 100644 --- a/src/agentseam/vendor_config.py +++ b/src/agentseam/vendor_config.py @@ -17,4 +17,10 @@ VENDOR_CONFIG = {agent: load("vendors/%s.json" % agent) for agent in sorted(ADAPTERS)} -__all__ = ["SCHEMA", "VENDOR_CONFIG"] + +def loads_claude_plugins(agent): + """The recorded `loads_claude_plugins` row for `agent`, or None where none is recorded.""" + return VENDOR_CONFIG.get(agent, {}).get("loads_claude_plugins") + + +__all__ = ["SCHEMA", "VENDOR_CONFIG", "loads_claude_plugins"] diff --git a/tests/test_claude_plugins.py b/tests/test_claude_plugins.py new file mode 100644 index 0000000..ae32893 --- /dev/null +++ b/tests/test_claude_plugins.py @@ -0,0 +1,90 @@ +"""CC-2: each vendor entry records whether its agent loads Claude Code plugins, from CC-1 only.""" + +from __future__ import annotations + +import copy +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "tools")) + +from validate_vendor_config import validate # noqa: E402 + +from agentseam.vendor_config import SCHEMA, VENDOR_CONFIG, loads_claude_plugins # noqa: E402 + +FIELD = "loads_claude_plugins" +WITNESS_FILE = "witness/results/witness-grok-claude-plugin-2026-10-17.md" + +#: (loads, hooks, deny_reply) per agent, as org-plan discovery/2026-10-17-claude-plugin-compat.md records them. +DISCOVERY_ROWS = { + "antigravity": ("no", "n/a", "no"), + "claude_code": ("yes", "yes", "yes"), + "cline": ("no", "n/a", "n/a"), + "codex_cli": ("yes", "yes", "yes"), + "cursor": ("unknown", "unknown", "unknown"), + "devin": ("yes", "unknown", "partial"), + "factory_droid": ("yes", "yes", "yes"), + "gemini_cli": ("no", "n/a", "partial"), + "goose": ("partial", "yes", "partial"), + "grok": ("yes", "yes", "yes"), + "junie": ("yes", "yes", "partial"), + "kimi_code": ("no", "n/a", "n/a"), + "mistral_vibe": ("yes", "unknown", "unknown"), + "openhands": ("yes", "yes", "partial"), + "tabnine": ("unknown", "unknown", "partial"), + "vscode_copilot": ("yes", "yes", "yes"), + "windsurf": ("unknown", "unknown", "partial"), +} + + +def test_each_entry_records_the_discovery_row(): + assert set(DISCOVERY_ROWS) == set(VENDOR_CONFIG) + for agent, (loads, hooks, deny_reply) in DISCOVERY_ROWS.items(): + row = VENDOR_CONFIG[agent][FIELD] + assert (row["loads"], row["hooks"], row["deny_reply"]) == (loads, hooks, deny_reply), agent + assert "discovery/2026-10-17-claude-plugin-compat.md" in row["source"], agent + record = VENDOR_CONFIG[agent]["evidence"][FIELD] + assert record["basis"] in ("vendor-docs", "vendor-source"), agent + assert record["date"] == "2026-10-17", agent + + +def test_grok_carries_the_witness_and_no_other_agent_does(): + witnessed = {agent for agent, entry in VENDOR_CONFIG.items() if "witnessed" in entry[FIELD]} + assert witnessed == {"grok"} + note = VENDOR_CONFIG["grok"][FIELD]["witnessed"] + for needle in ("guard", "gate", "refus", "Grok Build 1.0.62", "2026-10-17", WITNESS_FILE, "25b9d9d"): + assert needle in note, needle + record = VENDOR_CONFIG["grok"]["evidence"][FIELD] + assert record["basis"] == "vendor-source" + assert record["test"].endswith("::test_grok_carries_the_witness_and_no_other_agent_does") + + +def test_the_field_travels_with_its_own_evidence(): + for agent, entry in VENDOR_CONFIG.items(): + assert (FIELD in entry) == (FIELD in entry["evidence"]), agent + + +def test_the_accessor_returns_the_recorded_row_and_none_where_unrecorded(): + assert loads_claude_plugins("grok") == VENDOR_CONFIG["grok"][FIELD] + assert loads_claude_plugins("goose")["loads"] == "partial" + assert loads_claude_plugins("copilot") is None # a matrix row with no vendor entry + assert loads_claude_plugins("no_such_agent") is None + + +def test_schema_rejects_a_verdict_outside_the_closed_vocabulary(): + mutated = copy.deepcopy(dict(VENDOR_CONFIG["cursor"])) + mutated[FIELD] = dict(mutated[FIELD], loads="probably") + assert validate(SCHEMA, mutated) + + +def test_schema_rejects_an_unknown_key_in_the_field(): + mutated = copy.deepcopy(dict(VENDOR_CONFIG["grok"])) + mutated[FIELD] = dict(mutated[FIELD], tested="yes") + assert validate(SCHEMA, mutated) + + +def test_no_adapter_reads_the_field(): + adapters_dir = ROOT / "src" / "agentseam" / "adapters" + for path in adapters_dir.glob("*.py"): + assert FIELD not in path.read_text(encoding="utf-8"), path.name diff --git a/tools/recount/claude_plugins.json b/tools/recount/claude_plugins.json new file mode 100644 index 0000000..5673a76 --- /dev/null +++ b/tools/recount/claude_plugins.json @@ -0,0 +1,206 @@ +{ + "antigravity": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "no", + "hooks": "n/a", + "how": "n/a; needs a root plugin.json in its own schema (additionalProperties: false)", + "loads": "no", + "notes": "own hooks reply with a top-level decision", + "payload": "own (camelCase toolCall)", + "source": "https://antigravity.google/docs/plugins (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "claude_code": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "yes", + "hooks": "yes", + "how": "native: marketplace install into ~/.claude/plugins/cache////; --plugin-dir loads in place", + "loads": "yes", + "notes": "hooks run when the plugin is enabled; HSO deny and exit 2 both block", + "payload": "Claude Code", + "source": "https://code.claude.com/docs/en/plugins/loading.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "cline": { + "basis": "vendor-source", + "entry": { + "deny_reply": "n/a", + "hooks": "n/a", + "how": "n/a; two package lanes only (Agent Plugins root plugin.json, Cline JS/TS modules)", + "loads": "no", + "payload": "n/a", + "source": "cline/cline@a42df8c sdk/packages/shared/src/storage/paths.ts (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "codex_cli": { + "basis": "vendor-source", + "entry": { + "deny_reply": "yes", + "hooks": "yes", + "how": "manifest lookup .codex-plugin/, then .claude-plugin/plugin.json; marketplace .claude-plugin/marketplace.json; codex plugin marketplace add then codex plugin add P@M; no bare-folder install", + "loads": "yes", + "notes": "hooks from the manifest's hooks, else hooks/hooks.json; per-hook trust state unmeasured; an HSO deny needs a non-empty permissionDecisionReason and exit 2 needs non-empty stderr; Sync handlers only", + "payload": "Claude Code snake_case, Codex tool names", + "source": "openai/codex@ce0323f codex-rs/exec-server-protocol/src/protocol.rs, codex-rs/core-plugins/src/loader.rs (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "cursor": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "unknown", + "hooks": "unknown", + "how": "n/a for the documented formats (.cursor-plugin/plugin.json and a root plugin.json); unknown via the Third-Party Plugins toggle", + "loads": "unknown", + "notes": "recorded no for the documented plugin formats and unknown via the Third-Party Plugins toggle, so unknown overall; Claude Code settings hooks (not plugins) run by default, event and tool names mapped (Bash to Shell), HSO and exit 2 honoured", + "payload": "unknown", + "source": "https://cursor.com/docs/reference/plugins, https://cursor.com/docs/reference/third-party-hooks (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "devin": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "partial", + "hooks": "unknown", + "how": "devin plugins install --local ./dir or a repo (sign-in needed); falls back to .claude-plugin/plugin.json when no .devin-plugin/plugin.json", + "loads": "yes", + "notes": "the docs promise only a root hooks.json; plugin hooks are best effort and fail open; exit 2 and a top-level decision block, HSO permissionDecision undocumented", + "payload": "Claude-shaped snake_case, Devin tool names (exec)", + "source": "https://docs.devin.ai/cli/extensibility/plugins/overview (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "factory_droid": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "yes", + "hooks": "yes", + "how": "via a marketplace only: droid plugin marketplace add then droid plugin install p@m --scope user|project; .claude-plugin/ translated to .factory-plugin/ on cache copy; a bare plugin dir is rejected", + "loads": "yes", + "notes": "hooks run when enabled; a Claude Bash matcher may not match Droid's tool names (unmeasured); exit 2 and HSO deny both block", + "payload": "Claude Code snake_case, Droid tool names (Execute, Create)", + "source": "https://docs.factory.com/harness/plugins.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "gemini_cli": { + "basis": "vendor-source", + "entry": { + "deny_reply": "partial", + "hooks": "n/a", + "how": "n/a; extensions need gemini-extension.json", + "loads": "no", + "notes": "own hooks: exit 2 and a top-level decision; a one-shot gemini hooks migrate --from-claude exists", + "payload": "own events (BeforeTool), snake_case keys", + "source": "google-gemini/gemini-cli@ed96fae docs/extensions/reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "goose": { + "basis": "vendor-source", + "entry": { + "deny_reply": "partial", + "hooks": "yes", + "how": "place the folder in .agents/plugins// or ~/.agents/plugins// (loads by layout; .claude-plugin/plugin.json is not a goose manifest); no ~/.claude/plugins, no Claude marketplace", + "loads": "partial", + "notes": "hooks/hooks.json runs; exit 2 or {\"decision\":\"block\"} blocks; HSO is not parsed", + "payload": "own: key event, not hook_event_name; goose tool names (developer__shell)", + "source": "block/goose@bfcc6ee crates/goose/src/plugins/formats/open_plugins.rs, crates/goose/src/hooks/mod.rs (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "grok": { + "basis": "vendor-source", + "entry": { + "deny_reply": "yes", + "hooks": "yes", + "how": "scans ~/.claude/plugins/*/ (user, auto-trusted) and .claude/plugins/*/ (project, needs trust), plus installed_plugins.json; grok plugin install --trust; manifest fallback .claude-plugin/plugin.json; off until enabled", + "loads": "yes", + "notes": "hooks run when enabled and trusted; HSO deny and exit 2 block, other failures fail open", + "payload": "grok's camelCase envelope with additive Claude snake_case aliases; camelCase authoritative; grok tool names (run_terminal_command)", + "source": "xai-org/grok-build@2bdd1d6 crates/codegen/xai-grok-agent/src/plugins/discovery.rs, docs/user-guide/10-hooks.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)", + "witnessed": "guard and gate refusal: Grok Build 1.0.62 on Windows 11, 2026-10-17; guard and gate refused with the plugin installed by Claude Code, gate refused with it installed by grok plugin install --trust (org-plan witness/results/witness-grok-claude-plugin-2026-10-17.md at 25b9d9d)" + } + }, + "junie": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "partial", + "hooks": "yes", + "how": "through a Claude-format marketplace: /extensions marketplace add with a .claude-plugin/marketplace.json index; interactive install; cached in ~/.junie/extensions/", + "loads": "yes", + "notes": "per-extension manifest undocumented; hooks are Early Access; exit 2 and a top-level decision block, HSO not documented as blocking", + "payload": "Claude Code (hook_event_name, tool_name: \"Bash\")", + "source": "https://junie.jetbrains.com/docs/junie-cli-extensions.html (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "kimi_code": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "n/a", + "hooks": "n/a", + "how": "n/a; manifest is kimi.plugin.json or .kimi-plugin/plugin.json only", + "loads": "no", + "notes": "own hooks are Claude-shaped snake_case and honour HSO and exit 2", + "payload": "n/a", + "source": "https://www.kimi.com/code/docs/en/kimi-code-cli/customization/plugins.html (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "mistral_vibe": { + "basis": "vendor-source", + "entry": { + "deny_reply": "unknown", + "hooks": "unknown", + "how": "detected and adapted: auto-discovery under ~/.vibe/plugins/ or .vibe/plugins/ (trusted folder); no install command", + "loads": "yes", + "notes": "the adapter reads hooks/hooks.json into HookProtocol.CLAUDE_CODE definitions, but no executor for them was found, and the vendor's two skill docs contradict each other", + "payload": "unknown", + "source": "mistralai/mistral-vibe@4ae5c59 vibe/core/plugins/_claude.py (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "openhands": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "partial", + "hooks": "yes", + "how": "openhands --plugin /path; .claude-plugin/ is a supported metadata dir; marketplaces from .claude-plugin/marketplace.json", + "loads": "yes", + "notes": "hooks/hooks.json runs; exit 2 and a top-level decision: deny block; HSO is not parsed", + "payload": "mixed: snake_case tool_name/tool_input, but event key event_type (not hook_event_name); OpenHands tool names", + "source": "https://docs.openhands.dev/overview/plugins; OpenHands/software-agent-sdk@5966c78 openhands-sdk/openhands/sdk/hooks/types.py (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "tabnine": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "partial", + "hooks": "unknown", + "how": "tabnine extensions install translates if necessary; Claude never named", + "loads": "unknown", + "notes": "own hooks: exit 2 and decision: deny; the CLI is in maintenance mode until 2026-12-31", + "payload": "own event names (BeforeTool)", + "source": "https://docs.tabnine.com/main/getting-started/tabnine-cli/features/extensions (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "vscode_copilot": { + "basis": "vendor-source", + "entry": { + "deny_reply": "yes", + "hooks": "yes", + "how": "Claude-format plugin by manifest location, from a marketplace (.claude-plugin/marketplace.json, anthropics/claude-code accepted) or chat.pluginLocations; needs chat.plugins.enabled", + "loads": "yes", + "notes": "the Local harness ignores matchers, so every nested command for the event runs; HSO deny and exit 2 block", + "payload": "Claude-shaped snake_case, but VS Code's own Local tool names", + "source": "microsoft/vscode-docs@ef0ca127 docs/agents/reference/agent-plugin-reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + }, + "windsurf": { + "basis": "vendor-docs", + "entry": { + "deny_reply": "partial", + "hooks": "unknown", + "how": "no plugin system documented", + "loads": "unknown", + "notes": "own hooks: exit 2 only", + "payload": "own (agent_action_name, tool_info)", + "source": "https://docs.devin.ai/desktop/cascade/hooks (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + } + } +} diff --git a/tools/recount/sourced.py b/tools/recount/sourced.py index af5a63c..7dceece 100644 --- a/tools/recount/sourced.py +++ b/tools/recount/sourced.py @@ -6,6 +6,9 @@ from __future__ import annotations +import json +import os + #: The vendor's own token for the repository/project root, usable inside a hook command. #: claude_code: code.claude.com/docs/en/hooks ("Reference scripts by path", read #: 2026-09-01) -- ${CLAUDE_PROJECT_DIR} is "the project root where the session started", @@ -21,6 +24,15 @@ #: (event, command, url, matcher) (discovery.rs:233), so only an identical hook runs once. ALSO_READS = {"grok": [".claude/settings.json", ".claude/settings.local.json", ".cursor/hooks.json"]} +#: Whether each agent loads a Claude Code plugin, transcribed from org-plan +#: discovery/2026-10-17-claude-plugin-compat.md (CC-1, read 2026-10-17); unknown and partial +#: rows stay so. Grok alone adds the owner's witness run (witness/results at org-plan 25b9d9d). +with open(os.path.join(os.path.dirname(__file__), "claude_plugins.json"), encoding="utf-8") as _fh: + LOADS_CLAUDE_PLUGINS = json.load(_fh) +_CLAUDE_PLUGINS_READ = "2026-10-17" +_CLAUDE_PLUGINS_TEST = "tests/test_claude_plugins.py::test_each_entry_records_the_discovery_row" +_CLAUDE_PLUGINS_WITNESS_TEST = "tests/test_claude_plugins.py::test_grok_carries_the_witness_and_no_other_agent_does" + #: Repo hook configs this agent read before its current `config_path`, and still reads as a #: fallback. windsurf: docs.devin.ai/desktop/cascade/hooks (read 2026-10-13), Workspace-Level -- #: ".devin/hooks.json in your workspace root (the legacy .windsurf/hooks.json is used only when @@ -200,6 +212,11 @@ def apply(agent, entry): if agent in TRUST_HINT: entry["needs_trust"] = True entry["trust_hint"] = TRUST_HINT[agent] + if agent in LOADS_CLAUDE_PLUGINS: + row = LOADS_CLAUDE_PLUGINS[agent] + entry["loads_claude_plugins"] = dict(row["entry"]) + test = _CLAUDE_PLUGINS_WITNESS_TEST if "witnessed" in row["entry"] else _CLAUDE_PLUGINS_TEST + entry["evidence"]["loads_claude_plugins"] = {"basis": row["basis"], "date": _CLAUDE_PLUGINS_READ, "test": test} for (owner, claim), record in EVIDENCE.items(): if owner == agent: entry["evidence"][claim] = dict(record) From b36387199bc45e731a02a233c74b36f1f901c42b Mon Sep 17 00:00:00 2001 From: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com> Date: Sat, 17 Oct 2026 19:16:56 +0000 Subject: [PATCH 2/2] fix(vendors): n/a or unknown deny_reply where no Claude plugin loads; Copilot CLI row on vscode_copilot Orchestrator review of CC-2: deny_reply describes a Claude plugin deny, so it is n/a where loads is no and unknown where loads is unknown; CC-1 own-hook notes move to notes. vscode_copilot notes now carry the CC-1 Copilot CLI row and its source. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Jothi Mani Rajendran <250249270+jothimani-rajendran@users.noreply.github.com> --- src/agentseam/data/vendors/antigravity.json | 4 +-- src/agentseam/data/vendors/gemini_cli.json | 4 +-- src/agentseam/data/vendors/tabnine.json | 4 +-- .../data/vendors/vscode_copilot.json | 4 +-- src/agentseam/data/vendors/windsurf.json | 4 +-- tests/test_claude_plugins.py | 28 ++++++++++++++++--- tools/recount/claude_plugins.json | 20 ++++++------- 7 files changed, 44 insertions(+), 24 deletions(-) diff --git a/src/agentseam/data/vendors/antigravity.json b/src/agentseam/data/vendors/antigravity.json index 8b4e6e9..9150a66 100644 --- a/src/agentseam/data/vendors/antigravity.json +++ b/src/agentseam/data/vendors/antigravity.json @@ -98,11 +98,11 @@ "wrapper": "hooks_map" }, "loads_claude_plugins": { - "deny_reply": "no", + "deny_reply": "n/a", "hooks": "n/a", "how": "n/a; needs a root plugin.json in its own schema (additionalProperties: false)", "loads": "no", - "notes": "own hooks reply with a top-level decision", + "notes": "own hooks (not a Claude plugin) reply with a top-level decision, which CC-1 records as not honouring Claude's deny", "payload": "own (camelCase toolCall)", "source": "https://antigravity.google/docs/plugins (org-plan discovery/2026-10-17-claude-plugin-compat.md)" }, diff --git a/src/agentseam/data/vendors/gemini_cli.json b/src/agentseam/data/vendors/gemini_cli.json index 8f56f38..e843066 100644 --- a/src/agentseam/data/vendors/gemini_cli.json +++ b/src/agentseam/data/vendors/gemini_cli.json @@ -119,11 +119,11 @@ "wrapper": "hooks_map" }, "loads_claude_plugins": { - "deny_reply": "partial", + "deny_reply": "n/a", "hooks": "n/a", "how": "n/a; extensions need gemini-extension.json", "loads": "no", - "notes": "own hooks: exit 2 and a top-level decision; a one-shot gemini hooks migrate --from-claude exists", + "notes": "own hooks (not a Claude plugin): exit 2 and a top-level decision, recorded partial by CC-1; a one-shot gemini hooks migrate --from-claude exists", "payload": "own events (BeforeTool), snake_case keys", "source": "google-gemini/gemini-cli@ed96fae docs/extensions/reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" }, diff --git a/src/agentseam/data/vendors/tabnine.json b/src/agentseam/data/vendors/tabnine.json index 70ebfa1..58b795b 100644 --- a/src/agentseam/data/vendors/tabnine.json +++ b/src/agentseam/data/vendors/tabnine.json @@ -107,11 +107,11 @@ "wrapper": "hooks_map" }, "loads_claude_plugins": { - "deny_reply": "partial", + "deny_reply": "unknown", "hooks": "unknown", "how": "tabnine extensions install translates if necessary; Claude never named", "loads": "unknown", - "notes": "own hooks: exit 2 and decision: deny; the CLI is in maintenance mode until 2026-12-31", + "notes": "own hooks (not a Claude plugin): exit 2 and decision: deny, recorded partial by CC-1; the CLI is in maintenance mode until 2026-12-31", "payload": "own event names (BeforeTool)", "source": "https://docs.tabnine.com/main/getting-started/tabnine-cli/features/extensions (org-plan discovery/2026-10-17-claude-plugin-compat.md)" }, diff --git a/src/agentseam/data/vendors/vscode_copilot.json b/src/agentseam/data/vendors/vscode_copilot.json index fd3c7f2..fddc78c 100644 --- a/src/agentseam/data/vendors/vscode_copilot.json +++ b/src/agentseam/data/vendors/vscode_copilot.json @@ -148,9 +148,9 @@ "hooks": "yes", "how": "Claude-format plugin by manifest location, from a marketplace (.claude-plugin/marketplace.json, anthropics/claude-code accepted) or chat.pluginLocations; needs chat.plugins.enabled", "loads": "yes", - "notes": "the Local harness ignores matchers, so every nested command for the event runs; HSO deny and exit 2 block", + "notes": "VS Code: the Local harness ignores matchers, so every nested command for the event runs; HSO deny and exit 2 block. Copilot CLI (chock's copilot client builds for this entry): loads yes via copilot plugin install or --plugin-dir, legacy manifest includes .claude-plugin/plugin.json, copilot plugin marketplace add anthropics/claude-code; hooks yes (hooks.json or hooks/hooks.json); payload Claude Code for PascalCase events (Claude tool names, e.g. Bash), Copilot camelCase otherwise; deny partial: exit 2 denies, flat permissionDecision documented, nested hookSpecificOutput unknown (closed source)", "payload": "Claude-shaped snake_case, but VS Code's own Local tool names", - "source": "microsoft/vscode-docs@ef0ca127 docs/agents/reference/agent-plugin-reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + "source": "microsoft/vscode-docs@ef0ca127 docs/agents/reference/agent-plugin-reference.md; Copilot CLI: github/docs@dae83f7d content/copilot/reference/copilot-cli-reference/cli-plugin-reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" }, "needs_trust": false, "tools": { diff --git a/src/agentseam/data/vendors/windsurf.json b/src/agentseam/data/vendors/windsurf.json index ab14012..6608ab0 100644 --- a/src/agentseam/data/vendors/windsurf.json +++ b/src/agentseam/data/vendors/windsurf.json @@ -107,11 +107,11 @@ ".windsurf/hooks.json" ], "loads_claude_plugins": { - "deny_reply": "partial", + "deny_reply": "unknown", "hooks": "unknown", "how": "no plugin system documented", "loads": "unknown", - "notes": "own hooks: exit 2 only", + "notes": "own hooks (not a Claude plugin): exit 2 only, recorded partial by CC-1", "payload": "own (agent_action_name, tool_info)", "source": "https://docs.devin.ai/desktop/cascade/hooks (org-plan discovery/2026-10-17-claude-plugin-compat.md)" }, diff --git a/tests/test_claude_plugins.py b/tests/test_claude_plugins.py index ae32893..e39a5a5 100644 --- a/tests/test_claude_plugins.py +++ b/tests/test_claude_plugins.py @@ -18,23 +18,23 @@ #: (loads, hooks, deny_reply) per agent, as org-plan discovery/2026-10-17-claude-plugin-compat.md records them. DISCOVERY_ROWS = { - "antigravity": ("no", "n/a", "no"), + "antigravity": ("no", "n/a", "n/a"), "claude_code": ("yes", "yes", "yes"), "cline": ("no", "n/a", "n/a"), "codex_cli": ("yes", "yes", "yes"), "cursor": ("unknown", "unknown", "unknown"), "devin": ("yes", "unknown", "partial"), "factory_droid": ("yes", "yes", "yes"), - "gemini_cli": ("no", "n/a", "partial"), + "gemini_cli": ("no", "n/a", "n/a"), "goose": ("partial", "yes", "partial"), "grok": ("yes", "yes", "yes"), "junie": ("yes", "yes", "partial"), "kimi_code": ("no", "n/a", "n/a"), "mistral_vibe": ("yes", "unknown", "unknown"), "openhands": ("yes", "yes", "partial"), - "tabnine": ("unknown", "unknown", "partial"), + "tabnine": ("unknown", "unknown", "unknown"), "vscode_copilot": ("yes", "yes", "yes"), - "windsurf": ("unknown", "unknown", "partial"), + "windsurf": ("unknown", "unknown", "unknown"), } @@ -60,6 +60,26 @@ def test_grok_carries_the_witness_and_no_other_agent_does(): assert record["test"].endswith("::test_grok_carries_the_witness_and_no_other_agent_does") +def test_a_no_or_unknown_load_never_reports_a_hook_or_deny_verdict(): + implied = {"no": "n/a", "unknown": "unknown"} + for agent, entry in VENDOR_CONFIG.items(): + row = entry[FIELD] + if row["loads"] in implied: + assert (row["hooks"], row["deny_reply"]) == (implied[row["loads"]],) * 2, agent + + +def test_vscode_copilot_also_records_the_copilot_cli_row(): + row = VENDOR_CONFIG["vscode_copilot"][FIELD] + for needle in ( + "Copilot CLI", + "copilot plugin install", + ".claude-plugin/plugin.json", + "nested hookSpecificOutput unknown", + ): + assert needle in row["notes"], needle + assert "github/docs@dae83f7d" in row["source"] + + def test_the_field_travels_with_its_own_evidence(): for agent, entry in VENDOR_CONFIG.items(): assert (FIELD in entry) == (FIELD in entry["evidence"]), agent diff --git a/tools/recount/claude_plugins.json b/tools/recount/claude_plugins.json index 5673a76..d467dd1 100644 --- a/tools/recount/claude_plugins.json +++ b/tools/recount/claude_plugins.json @@ -2,11 +2,11 @@ "antigravity": { "basis": "vendor-docs", "entry": { - "deny_reply": "no", + "deny_reply": "n/a", "hooks": "n/a", "how": "n/a; needs a root plugin.json in its own schema (additionalProperties: false)", "loads": "no", - "notes": "own hooks reply with a top-level decision", + "notes": "own hooks (not a Claude plugin) reply with a top-level decision, which CC-1 records as not honouring Claude's deny", "payload": "own (camelCase toolCall)", "source": "https://antigravity.google/docs/plugins (org-plan discovery/2026-10-17-claude-plugin-compat.md)" } @@ -85,11 +85,11 @@ "gemini_cli": { "basis": "vendor-source", "entry": { - "deny_reply": "partial", + "deny_reply": "n/a", "hooks": "n/a", "how": "n/a; extensions need gemini-extension.json", "loads": "no", - "notes": "own hooks: exit 2 and a top-level decision; a one-shot gemini hooks migrate --from-claude exists", + "notes": "own hooks (not a Claude plugin): exit 2 and a top-level decision, recorded partial by CC-1; a one-shot gemini hooks migrate --from-claude exists", "payload": "own events (BeforeTool), snake_case keys", "source": "google-gemini/gemini-cli@ed96fae docs/extensions/reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" } @@ -170,11 +170,11 @@ "tabnine": { "basis": "vendor-docs", "entry": { - "deny_reply": "partial", + "deny_reply": "unknown", "hooks": "unknown", "how": "tabnine extensions install translates if necessary; Claude never named", "loads": "unknown", - "notes": "own hooks: exit 2 and decision: deny; the CLI is in maintenance mode until 2026-12-31", + "notes": "own hooks (not a Claude plugin): exit 2 and decision: deny, recorded partial by CC-1; the CLI is in maintenance mode until 2026-12-31", "payload": "own event names (BeforeTool)", "source": "https://docs.tabnine.com/main/getting-started/tabnine-cli/features/extensions (org-plan discovery/2026-10-17-claude-plugin-compat.md)" } @@ -186,19 +186,19 @@ "hooks": "yes", "how": "Claude-format plugin by manifest location, from a marketplace (.claude-plugin/marketplace.json, anthropics/claude-code accepted) or chat.pluginLocations; needs chat.plugins.enabled", "loads": "yes", - "notes": "the Local harness ignores matchers, so every nested command for the event runs; HSO deny and exit 2 block", + "notes": "VS Code: the Local harness ignores matchers, so every nested command for the event runs; HSO deny and exit 2 block. Copilot CLI (chock's copilot client builds for this entry): loads yes via copilot plugin install or --plugin-dir, legacy manifest includes .claude-plugin/plugin.json, copilot plugin marketplace add anthropics/claude-code; hooks yes (hooks.json or hooks/hooks.json); payload Claude Code for PascalCase events (Claude tool names, e.g. Bash), Copilot camelCase otherwise; deny partial: exit 2 denies, flat permissionDecision documented, nested hookSpecificOutput unknown (closed source)", "payload": "Claude-shaped snake_case, but VS Code's own Local tool names", - "source": "microsoft/vscode-docs@ef0ca127 docs/agents/reference/agent-plugin-reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" + "source": "microsoft/vscode-docs@ef0ca127 docs/agents/reference/agent-plugin-reference.md; Copilot CLI: github/docs@dae83f7d content/copilot/reference/copilot-cli-reference/cli-plugin-reference.md (org-plan discovery/2026-10-17-claude-plugin-compat.md)" } }, "windsurf": { "basis": "vendor-docs", "entry": { - "deny_reply": "partial", + "deny_reply": "unknown", "hooks": "unknown", "how": "no plugin system documented", "loads": "unknown", - "notes": "own hooks: exit 2 only", + "notes": "own hooks (not a Claude plugin): exit 2 only, recorded partial by CC-1", "payload": "own (agent_action_name, tool_info)", "source": "https://docs.devin.ai/desktop/cascade/hooks (org-plan discovery/2026-10-17-claude-plugin-compat.md)" }