Skip to content
Open
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
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,12 @@ Two files make the vouch repo loadable directly as an OpenClaw plugin

* [`openclaw.plugin.json`](./openclaw.plugin.json) — the manifest the
loader parses: `id` + `configSchema` (required), `kind: context-engine`,
`version`, and `skills` (directories under `adapters/openclaw/skills/`,
one SKILL.md each; OpenClaw publishes them as skills *and* slash
commands).
`version`, the `openclaw.compat.pluginApi` floor, and `skills`
(directories under `adapters/openclaw/skills/`, one SKILL.md each;
OpenClaw publishes them as skills *and* slash commands).
* [`package.json`](./package.json) — loader-facing only: the
`openclaw.extensions` pointer at the JS entry module and the
`openclaw.compat.pluginApi` floor. The Python package stays in
matching `openclaw.compat.pluginApi` floor. The Python package stays in
`pyproject.toml`.

Invariants, all enforced by `tests/test_openclaw_plugin_manifest.py`:
Expand Down
5 changes: 5 additions & 0 deletions openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"version": "1.2.2",
"description": "Git-native, review-gated knowledge base. Registers vouch's context engine (cited retrieval + salience reflex + hot memory) and the vouch skills. The kb.* MCP server is deployment config: `openclaw mcp add vouch -- vouch serve`.",
"kind": "context-engine",
"openclaw": {
"compat": {
"pluginApi": ">=2026.6.0"
}
},
"skills": [
"adapters/openclaw/skills"
],
Expand Down
11 changes: 11 additions & 0 deletions schemas/capabilities.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@
"title": "Context Engines",
"type": "array"
},
"host_compat": {
"additionalProperties": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"description": "Host compatibility metadata keyed by host/plugin id.",
"title": "Host Compat",
"type": "object"
},
"knowledge_capability": {
"additionalProperties": true,
"title": "Knowledge Capability",
Expand Down
3 changes: 3 additions & 0 deletions src/vouch/capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from .models import Capabilities
from .openclaw.context_engine import describe_engine

OPENCLAW_PLUGIN_API = ">=2026.6.0"

# The full method surface this implementation exposes. Keep this list in
# sync with the MCP server + JSONL server registrations — `test_capabilities`
# asserts they match.
Expand Down Expand Up @@ -98,4 +100,5 @@ def capabilities() -> Capabilities:
"config_path": "retrieval.scope",
},
context_engines=[describe_engine()],
host_compat={"openclaw": {"pluginApi": OPENCLAW_PLUGIN_API}},
)
4 changes: 4 additions & 0 deletions src/vouch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,7 @@ class Capabilities(BaseModel):
default_factory=list,
description="OpenClaw context engines exposed (see openclaw.plugin.json)",
)
host_compat: dict[str, dict[str, str]] = Field(
default_factory=dict,
description="Host compatibility metadata keyed by host/plugin id.",
)
23 changes: 23 additions & 0 deletions tests/test_capabilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@

from __future__ import annotations

import json
from pathlib import Path

from vouch import capabilities
from vouch.jsonl_server import HANDLERS

REPO_ROOT = Path(__file__).resolve().parents[1]
MANIFEST_PATH = REPO_ROOT / "openclaw.plugin.json"


def _manifest_plugin_api() -> str:
manifest = json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
return manifest["openclaw"]["compat"]["pluginApi"]


def test_capabilities_matches_jsonl_handlers() -> None:
caps = capabilities.capabilities()
Expand All @@ -15,3 +26,15 @@ def test_capabilities_matches_jsonl_handlers() -> None:
f"missing handlers={declared - implemented}, "
f"missing capabilities={implemented - declared}"
)


def test_capabilities_host_compat_matches_openclaw_manifest() -> None:
caps = capabilities.capabilities()
manifest_range = _manifest_plugin_api()
assert caps.host_compat["openclaw"]["pluginApi"] == manifest_range


def test_capabilities_host_compat_present_and_nonempty() -> None:
caps = capabilities.capabilities()
assert "openclaw" in caps.host_compat
assert caps.host_compat["openclaw"]["pluginApi"]
7 changes: 6 additions & 1 deletion tests/test_openclaw_plugin_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ def test_package_json_declares_entry_and_compat(package_json: dict) -> None:
assert openclaw["compat"]["pluginApi"]


def test_manifest_declares_openclaw_compat(manifest: dict, package_json: dict) -> None:
assert manifest["openclaw"]["compat"]["pluginApi"] == (
package_json["openclaw"]["compat"]["pluginApi"]
)


def test_manifest_skills_are_publishable_dirs(manifest: dict) -> None:
"""Each skills entry must resolve to dirs OpenClaw can publish.

Expand Down Expand Up @@ -144,7 +150,6 @@ def test_manifest_carries_no_dead_dialect_fields(manifest: dict) -> None:
"mcpServers",
"shared_deps",
"excluded_from_install",
"openclaw",
"contracts",
)
for dead in dead_fields:
Expand Down
Loading