
Vet
- 1.1k installs
- 600 repo stars
- Updated June 10, 2026
- imbue-ai/vet
vet provides documented workflows for Run vet immediately after ANY logical unit of code changes. Do not batch your changes, do not wait to be asked to run vet, make sure you are proactive.
About
The vet skill run vet immediately after ANY logical unit of code changes. Do not batch your changes, do not wait to be asked to run vet, make sure you are proactive. # Vet **Run vet immediately after ANY logical unit of code changes. Do not batch changes, do not wait to be asked to run vet, make sure you are proactive.** Vet reviews git diffs and conversation history to find issues in code changes and conversation history. It is most effective when run frequently with conversation history, which helps it catch misunderstandings between what was requested and what was implemented. Despite this, vet is not a replacement for running tests. ## Installation Install vet using pip, pipx, or uv: ```bash # Using pip pip install verify-everything # Using pipx pipx install verify-everything # Using uv uv tool install verify-everything # Verify installation: vet --help ``` ## Running Vet ### Standard Usage Before running vet, determine the correct Python binary: ```bash $(command -v python3 || command -v python) ``` Use whichever resolves (prefer `python3`).
- Run: `opencode session list --format json` to list recent sessions with their IDs and titles.
- Identify the current session from the list by matching the title or timestamp.
- IMPORTANT: Verify the session you found matches the current conversation. If the title is ambiguous, compare timestamps
- Pass the session ID as `--session-id`.
- Find the most unique sentence / question / string in the current conversation.
Vet by the numbers
- 1,090 all-time installs (skills.sh)
- Ranked #221 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
vet capabilities & compatibility
- Capabilities
- run: `opencode session list format json` to li · identify the current session from the list by ma · important: verify the session you found matches · pass the session id as ` session id`. · find the most unique sentence / question / strin
- Use cases
- documentation
What vet says it does
# Vet **Run vet immediately after ANY logical unit of code changes.
Do not batch changes, do not wait to be asked to run vet, make sure you are proactive.** Vet reviews git diffs and conversation history to find issues in code changes and conversation history.
npx skills add https://github.com/imbue-ai/vet --skill vetAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 600 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 10, 2026 |
| Repository | imbue-ai/vet ↗ |
How do I use vet for the task described in its SKILL.md triggers?
Run vet immediately after ANY logical unit of code changes. Do not batch your changes, do not wait to be asked to run vet, make sure you are proactive.
Who is it for?
Teams invoking vet when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Run vet immediately after ANY logical unit of code changes. Do not batch your changes, do not wait to be asked to run vet, make sure you are proactive.
What you get
Step-by-step guidance grounded in vet documentation and reference files.
- drift and logic issue findings
- diff-to-requirement mapping notes
Files
Vet
Run vet immediately after ANY logical unit of code changes. Do not batch changes, do not wait to be asked to run vet, make sure you are proactive.
Vet reviews git diffs and conversation history to find issues in code changes and conversation history. It is most effective when run frequently with conversation history, which helps it catch misunderstandings between what was requested and what was implemented. Despite this, vet is not a replacement for running tests.
Installation
Install vet using pip, pipx, or uv:
# Using pip
pip install verify-everything
# Using pipx
pipx install verify-everything
# Using uv
uv tool install verify-everything
# Verify installation:
vet --helpRunning Vet
Standard Usage
Before running vet, determine the correct Python binary:
$(command -v python3 || command -v python)Use whichever resolves (prefer python3). The examples below use python3, substitute python if that is what your system provides.
OpenCode:
vet "goal" --history-loader "python3 ~/.agents/skills/vet/scripts/export_opencode_session.py --session-id <ses_ID>"Codex:
vet "goal" --history-loader "python3 ~/.codex/skills/vet/scripts/export_codex_session.py --session-file <path-to-session.jsonl>"Claude Code:
vet "goal" --history-loader "python3 ~/.claude/skills/vet/scripts/export_claude_code_session.py --session-file <path-to-session.jsonl>"Gemini CLI:
vet "goal" --history-loader "python3 ~/.gemini/skills/vet/scripts/export_gemini_cli_session.py --session-file <path-to-session.json>"Without Conversation History
vet "goal"Finding Your Session
You should only search for sessions from your coding harness. If a user requests you use a different harness, they are likely referring to vet's agentic mode, not the session.
OpenCode: The --session-id argument requires a ses_... session ID. To find the current session ID: 1. Run: opencode session list --format json to list recent sessions with their IDs and titles. 2. Identify the current session from the list by matching the title or timestamp.
- IMPORTANT: Verify the session you found matches the current conversation. If the title is ambiguous, compare timestamps or check multiple candidates.
3. Pass the session ID as --session-id.
Codex: Session files are stored in ~/.codex/sessions/YYYY/MM/DD/. To find the correct session file: 1. Find the most unique sentence / question / string in the current conversation. 2. Run: grep -rl "UNIQUE_MESSAGE" ~/.codex/sessions/ to find the matching session file.
- IMPORTANT: Verify the conversation you found matches the current conversation and that it is not another conversation with the same search string.
3. Pass the matched file path as --session-file.
Claude Code: Your current session UUID is ${CLAUDE_SESSION_ID}. Session files are stored in ~/.claude/projects/<encoded-path>/ as <session-uuid>.jsonl. Find the session file matching your UUID and verify it belongs to this conversation. If the UUID above was not replaced with an actual value (e.g. older Claude Code versions), fall back to a manual search: 1. Find the most unique sentence / question / string in the current conversation. 2. Run: grep -rl "UNIQUE_MESSAGE" ~/.claude/projects/ to find the matching session file.
- IMPORTANT: Verify the conversation you found matches the current conversation and that it is not another conversation with the same search string.
3. Pass the matched file path as --session-file.
Gemini CLI: Session files are stored in ~/.gemini/tmp/<project-name>/chats/. To find the correct session file: 1. Find the most unique sentence / question / string in the current conversation. 2. Run: grep -rl "UNIQUE_MESSAGE" ~/.gemini/tmp/ to find the matching session file.
- IMPORTANT: Verify the conversation you found matches the current conversation and that it is not another conversation with the same search string.
3. Pass the matched file path as --session-file.
NOTE: The examples in the standard usage section assume the user installed the vet skill at the user level, not the project level. Prior to trying to run vet, check if it was installed at the project level which should take precedence over the user level. If it is installed at the project level, ensure the history-loader option points to the correct location.
Interpreting Results
Vet analyzes the full git diff from the base commit. This may include changes from other agents or sessions working in the same repository. If vet reports issues that relate to changes you did not make in this session, disregard them, assuming they belong to another agent or the user.
Common Options
--base-commit REF: Git ref for diff base (default: HEAD)--model MODEL: LLM to use (default: claude-opus-4-8)--list-models: list all models that are supported by vet- Run
vet --helpand look at the vet repo's readme for details about defining custom OpenAI-compatible models. --update-models: fetch the latest community model definitions from the remote registry and cache them locally. See "Updating the Model Registry" below for when to run this.--confidence-threshold N: Minimum confidence 0.0-1.0 (default: 0.8)--output-format FORMAT: Output astext,json, orgithub--quiet: Suppress status messages and 'No issues found.'--agentic: Mode that routes analysis through the locally installed Claude Code, Codex, or OpenCode CLI instead of calling the API directly. Try this if vet fails due to missing API keys. This is slower so it is not the default, but it often results in higher precision issue identification.--modelis forwarded to the harness but not validated by vet, as vet doesn't know which models each harness supports.--agent-harness: The three options for this arecodex,claude, andopencode. Claude Code is the default.--help: Show comprehensive list of options
Updating
The vet CLI, skill files, and export scripts can become outdated as agent harnesses and LLM APIs change.
If this happens, try updating them. Run which vet to determine how vet was installed and update accordingly. For the skill files, check which skill directories exist on disk and update them with the latest versions from https://github.com/imbue-ai/vet/tree/main/skills/vet.
Updating the Model Registry
Run vet --update-models to fetch the latest community model definitions from the remote registry without upgrading vet itself. This caches model definitions locally so they appear in --list-models and can be used with --model.
You should run vet --update-models when:
- Vet reports an unknown or unrecognized model error.
vet --list-modelsdoes not show a model you or the user expects to be available.- The user explicitly asks you to update the model registry.
Additional Information
Additional information can be found in the vet repo:
https://github.com/imbue-ai/vet
#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path
parser = argparse.ArgumentParser(description="Export Claude Code session history for vet")
parser.add_argument("--session-file", required=True, help="Path to Claude Code session .jsonl file")
args = parser.parse_args()
SESSION_FILE = Path(args.session_file)
if not SESSION_FILE.exists():
sys.exit(0)
# Map tool_use_id -> (tool_name, tool_input) so ToolResultBlocks can reference the tool name
tool_use_info: dict[str, tuple[str, dict]] = {}
msg_counter = 0
for line in SESSION_FILE.read_text().splitlines():
if not line.strip():
continue
try:
entry = json.loads(line)
except json.JSONDecodeError as e:
print(
f"WARNING: Skipping malformed JSON line in {SESSION_FILE}: {e}",
file=sys.stderr,
)
continue
entry_type = entry.get("type")
if entry_type not in ("user", "assistant"):
continue
if entry.get("isSidechain"):
continue
message = entry.get("message", {})
content = message.get("content")
if entry_type == "user":
if isinstance(content, str) and content.strip():
print(json.dumps({"object_type": "ChatInputUserMessage", "text": content}))
elif isinstance(content, list):
text_parts = []
tool_result_blocks = []
for c in content:
if not isinstance(c, dict):
continue
if c.get("type") == "text" and c.get("text"):
text_parts.append(c["text"])
elif c.get("type") == "tool_result":
result_content = c.get("content", "")
if isinstance(result_content, list):
result_content = " ".join(
rc.get("text", "")
for rc in result_content
if isinstance(rc, dict) and rc.get("type") == "text"
)
tool_use_id = c.get("tool_use_id", "")
tool_name, tool_input = tool_use_info.get(tool_use_id, ("unknown", {}))
tool_result_blocks.append(
{
"object_type": "ToolResultBlock",
"type": "tool_result",
"tool_use_id": tool_use_id,
"tool_name": tool_name,
"invocation_string": f"{tool_name}({json.dumps(tool_input)})",
"content": {
"content_type": "generic",
"text": result_content,
},
}
)
text = " ".join(text_parts)
if text.strip():
print(json.dumps({"object_type": "ChatInputUserMessage", "text": text}))
if tool_result_blocks:
msg_counter += 1
print(
json.dumps(
{
"object_type": "ResponseBlockAgentMessage",
"role": "user",
"assistant_message_id": f"claude_code_tool_result_{msg_counter}",
"content": tool_result_blocks,
}
)
)
elif entry_type == "assistant":
if not isinstance(content, list):
continue
blocks = []
for c in content:
if not isinstance(c, dict):
continue
if c.get("type") == "text" and c.get("text"):
blocks.append({"object_type": "TextBlock", "type": "text", "text": c["text"]})
elif c.get("type") == "tool_use":
tool_use_id = c.get("id", "")
tool_name = c.get("name", "")
tool_input = c.get("input", {})
# Record for later ToolResultBlock lookups
tool_use_info[tool_use_id] = (tool_name, tool_input)
blocks.append(
{
"object_type": "ToolUseBlock",
"type": "tool_use",
"id": tool_use_id,
"name": tool_name,
"input": tool_input,
}
)
if blocks:
print(
json.dumps(
{
"object_type": "ResponseBlockAgentMessage",
"role": "assistant",
"assistant_message_id": message.get("id", entry.get("uuid", "claude_code_msg")),
"content": blocks,
}
)
)
#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path
parser = argparse.ArgumentParser(description="Export Codex session history for vet")
parser.add_argument("--session-file", required=True, help="Path to Codex session .jsonl file")
args = parser.parse_args()
SESSION_FILE = args.session_file
if not Path(SESSION_FILE).exists():
sys.exit(0)
# Map call_id -> (fn_name, fn_input) so ToolResultBlocks can reference the tool name
call_info: dict[str, tuple[str, dict]] = {}
# Buffer tool blocks so they can be wrapped in a ResponseBlockAgentMessage
tool_block_buffer: list[dict] = []
msg_counter = 0
def flush_tool_blocks() -> None:
"""Emit any buffered tool blocks wrapped in a ResponseBlockAgentMessage."""
global msg_counter
if not tool_block_buffer:
return
msg_counter += 1
print(
json.dumps(
{
"object_type": "ResponseBlockAgentMessage",
"role": "assistant",
"assistant_message_id": f"codex_tool_msg_{msg_counter}",
"content": list(tool_block_buffer),
}
)
)
tool_block_buffer.clear()
for line in Path(SESSION_FILE).read_text().splitlines():
if not line.strip():
continue
try:
entry = json.loads(line)
except json.JSONDecodeError as e:
print(
f"WARNING: Skipping malformed JSON line in {SESSION_FILE}: {e}",
file=sys.stderr,
)
continue
if entry.get("type") != "response_item":
continue
payload = entry.get("payload", {})
payload_type = payload.get("type")
if payload_type == "function_call":
call_id = payload.get("call_id", payload.get("id", ""))
fn_name = payload.get("name", "")
fn_args = payload.get("arguments", "")
# arguments is a JSON string in the Responses API; try to parse it
try:
fn_input = json.loads(fn_args) if isinstance(fn_args, str) else fn_args
except (json.JSONDecodeError, TypeError):
fn_input = {"raw": fn_args}
call_info[call_id] = (fn_name, fn_input)
tool_block_buffer.append(
{
"object_type": "ToolUseBlock",
"type": "tool_use",
"id": call_id,
"name": fn_name,
"input": fn_input,
}
)
continue
if payload_type == "function_call_output":
call_id = payload.get("call_id", "")
output = payload.get("output", "")
fn_name, fn_input = call_info.get(call_id, ("unknown", {}))
tool_block_buffer.append(
{
"object_type": "ToolResultBlock",
"type": "tool_result",
"tool_use_id": call_id,
"tool_name": fn_name,
"invocation_string": f"{fn_name}({json.dumps(fn_input)})",
"content": {"content_type": "generic", "text": output},
}
)
continue
if payload_type != "message":
continue
role = payload.get("role")
content = payload.get("content", [])
if role == "user":
# Flush any pending tool blocks before the user message
flush_tool_blocks()
text = " ".join(c.get("text", "") for c in content if c.get("type") == "input_text")
if text:
print(json.dumps({"object_type": "ChatInputUserMessage", "text": text}))
elif role == "assistant":
# Merge buffered tool blocks into this assistant message
blocks = list(tool_block_buffer)
tool_block_buffer.clear()
for c in content:
if c.get("type") == "output_text" and c.get("text"):
blocks.append({"object_type": "TextBlock", "type": "text", "text": c["text"]})
if blocks:
msg_counter += 1
print(
json.dumps(
{
"object_type": "ResponseBlockAgentMessage",
"role": "assistant",
"assistant_message_id": f"codex_msg_{msg_counter}",
"content": blocks,
}
)
)
# Flush any remaining tool blocks at end of file
flush_tool_blocks()
#!/usr/bin/env python3
import argparse
import json
import sys
from pathlib import Path
parser = argparse.ArgumentParser(description="Export Gemini CLI session history for vet")
parser.add_argument("--session-file", required=True, help="Path to Gemini CLI session .json file")
args = parser.parse_args()
SESSION_FILE = Path(args.session_file)
if not SESSION_FILE.exists():
print(f"ERROR: Session file {SESSION_FILE} does not exist", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(SESSION_FILE.read_text())
except json.JSONDecodeError as e:
print(f"ERROR: Failed to parse JSON from {SESSION_FILE}: {e}", file=sys.stderr)
sys.exit(1)
messages = data.get("messages", [])
for msg in messages:
msg_type = msg.get("type")
content = msg.get("content")
if msg_type == "user":
if isinstance(content, list):
text = " ".join(c.get("text", "") for c in content if isinstance(c, dict) and "text" in c)
else:
text = str(content)
if text:
print(json.dumps({"object_type": "ChatInputUserMessage", "text": text}))
elif msg_type == "gemini":
blocks = []
# Add the text content as a TextBlock if it exists
if content and isinstance(content, str):
blocks.append({"object_type": "TextBlock", "type": "text", "text": content})
# Process tool calls
tool_calls = msg.get("toolCalls", [])
for tc in tool_calls:
call_id = tc.get("id")
name = tc.get("name")
tool_args = tc.get("args")
blocks.append(
{
"object_type": "ToolUseBlock",
"type": "tool_use",
"id": call_id,
"name": name,
"input": tool_args,
}
)
# Process tool results
results = tc.get("result", [])
for res in results:
# Results can be complex; extract the response content
# Based on the example, it's often in functionResponse.response.output
output = ""
if "functionResponse" in res:
fr = res["functionResponse"]
response = fr.get("response", {})
if isinstance(response, dict):
output = response.get("output", "")
else:
output = str(response)
else:
output = str(res)
blocks.append(
{
"object_type": "ToolResultBlock",
"type": "tool_result",
"tool_use_id": call_id,
"tool_name": name,
"invocation_string": f"{name}({json.dumps(tool_args)})",
"content": {"content_type": "generic", "text": str(output)},
}
)
if blocks:
print(
json.dumps(
{
"object_type": "ResponseBlockAgentMessage",
"role": "assistant",
"assistant_message_id": msg.get("id"),
"content": blocks,
}
)
)
elif msg_type == "info":
# Info messages are usually system notifications, maybe skip or map to something else?
# For now, let's just skip them as they don't typically represent agent-user dialogue.
pass
#!/usr/bin/env python3
import argparse
import json
import os
import subprocess
import sys
import tempfile
parser = argparse.ArgumentParser(description="Export OpenCode session history for vet")
parser.add_argument("--session-id", required=True, help="OpenCode session ID (ses_...)")
args = parser.parse_args()
fd, tmppath = tempfile.mkstemp(suffix=".json")
try:
with os.fdopen(fd, "w+b") as f:
try:
result = subprocess.run(
["opencode", "export", args.session_id],
stdout=f,
stderr=subprocess.PIPE,
)
except (FileNotFoundError, OSError) as e:
print(
f"WARNING: Could not run 'opencode' command: {e}",
file=sys.stderr,
)
sys.exit(0)
if result.returncode != 0:
print(
f"WARNING: opencode export failed for session {args.session_id}: {result.stderr.decode().strip()}",
file=sys.stderr,
)
sys.exit(0)
with open(tmppath, "r") as f:
raw = f.read()
finally:
os.unlink(tmppath)
if not raw.strip():
print(
f"WARNING: opencode export returned empty output for session {args.session_id}",
file=sys.stderr,
)
sys.exit(0)
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
print(f"WARNING: Failed to parse opencode export output: {e}", file=sys.stderr)
sys.exit(0)
for msg in data.get("messages", []):
info = msg.get("info", {})
parts = msg.get("parts", [])
role = info.get("role", "user")
msg_id = info.get("id", "")
if role == "user":
text = " ".join(p.get("text", "") for p in parts if p.get("type") == "text")
if text:
print(json.dumps({"object_type": "ChatInputUserMessage", "text": text}))
else:
content = []
for p in parts:
if p.get("type") == "text" and p.get("text"):
content.append({"object_type": "TextBlock", "type": "text", "text": p["text"]})
elif p.get("type") == "tool":
call_id = p.get("callID", "")
tool_name = p.get("tool", "")
state = p.get("state", {})
tool_input = state.get("input", {})
tool_output = state.get("output", "")
content.append(
{
"object_type": "ToolUseBlock",
"type": "tool_use",
"id": call_id,
"name": tool_name,
"input": tool_input,
}
)
content.append(
{
"object_type": "ToolResultBlock",
"type": "tool_result",
"tool_use_id": call_id,
"tool_name": tool_name,
"invocation_string": f"{tool_name}({json.dumps(tool_input)})",
"content": {"content_type": "generic", "text": tool_output},
}
)
if content:
print(
json.dumps(
{
"object_type": "ResponseBlockAgentMessage",
"role": "assistant",
"assistant_message_id": msg_id,
"content": content,
}
)
)
Related skills
How it compares
Use vet for continuous conversational diff review; static analyzers and tests alone will not catch spec misunderstandings from chat context.
FAQ
What does vet do?
Run vet immediately after ANY logical unit of code changes. Do not batch your changes, do not wait to be asked to run vet, make sure you are proactive.
When should I use vet?
Run vet immediately after ANY logical unit of code changes. Do not batch your changes, do not wait to be asked to run vet, make sure you are proactive.
What are common prerequisites?
--- name: vet description: Run vet immediately after ANY logical unit of code changes.
Is Vet safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.