Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Deep-dive review of any GitHub PR in tetherto/qvac. Validates gitflow, CI, title/body format, code quality, security, and applicable repo rules. Posts a PENDING review with inline comments.
SkillScore50/100
Test results 50
3/3 safety checks passed
32%50%of test cases passed +18 pts
Passed 11 of 22 test cases with the skill, and 7 of 22 without it.
6 fixed5 passed either way9 still failing2 broke
One test taskWhat exact gh api command should be presented to the user before submitting the pending review for PR https://github.com/tetherto/qvac/pull/1124?
Adds every file the skill has, pinned so you can update it. No account needed.
pip install "decimalai>=0.15"
decimalai skills add tetherto-qv-pr-review
claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"The same install from Python, for a notebook or a setup script.
pip install "decimalai>=0.15"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
print("Added. Start Claude Code with the command below.")claude --plugin-dir "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.agents"Type these in a Claude Code session. No account or API key. It writes the skill and any files it bundles, and asks first when its safety rating isn't Passed.
/plugin marketplace add decimal-labs/decimalai-claude-plugins
/plugin install decimalai@decimalai/decimalai:install tetherto-qv-pr-reviewThe plugin needs python3 and curl on your machine.
No Python or account. Claude Code picks it up in its next session.
curl -fsSL --create-dirs -o .claude/skills/tetherto-qv-pr-review/SKILL.md https://app.decimal.ai/s/tetherto-qv-pr-review/SKILL.md.claude/skills/tetherto-qv-pr-review/SKILL.mdThis gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"
decimalai skills add tetherto-qv-pr-reviewimport asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[claude-agent-sdk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from claude_agent_sdk import ClaudeAgentOptions
from decimalai.claude_agent_sdk import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
async def main():
question = "Hello!"
async with support.run(question) as run: # one user message, one run
async for message in run.query(options=ClaudeAgentOptions(max_turns=6)):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())Prefer its own loader? ClaudeAgentOptions(plugins=[skills_plugin()], skills="all") reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdimport asyncio
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions, query
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
options = ClaudeAgentOptions(system_prompt=f"You are a helpful agent.\n\n{skill}", max_turns=6)
async def main():
async for message in query(prompt="Hello!", options=options):
print(message)
# In a notebook or async code: await main()
asyncio.run(main())This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"
decimalai skills add tetherto-qv-pr-reviewfrom agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai-agents]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
from agents import Agent, Runner
from decimalai.openai_agents import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
name=support.name,
instructions=support.instructions, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")Using SandboxAgent? capabilities=[Shell(), decimalai.openai_agents.sandbox_skills()] points its own loader at the same folder.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from agents import Agent, Runner
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = Agent(name="support", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await Runner.run(agent, "Hello!")
result = Runner.run_sync(agent, "Hello!")This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"
decimalai skills add tetherto-qv-pr-reviewimport json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[openai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
import json
from openai import OpenAI
from decimalai.openai import agent_config
client = OpenAI()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your tools, as Chat Completions takes them, and what answers each.
TOOLS = [{"type": "function", "function": {
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
# run.system is your prompt, then the skill list.
messages = [{"role": "system", "content": run.system}, run.message]
while True:
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=messages,
tools=[*TOOLS, *run.tools], # your tools, then load_skill
).choices[0].message
messages.append(reply)
if not reply.tool_calls:
break
for call in reply.tool_calls:
# load_skill is answered by the run; your own tools by FUNCTIONS.
function = FUNCTIONS.get(call.function.name)
messages.append(run.tool_result(call) or {
"role": "tool", "tool_call_id": call.id,
"content": function(**json.loads(call.function.arguments)) if function
else f"There is no tool named {call.function.name!r}.",
})
print(reply.content)
# The Responses API takes run.response_tools instead of run.tools.No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from openai import OpenAI
client = OpenAI()
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
reply = client.chat.completions.create(
model="gpt-5.4-mini",
messages=[
{"role": "system", "content": f"You are a helpful agent.\n\n{skill}"},
{"role": "user", "content": "Hello!"},
],
).choices[0].messageThis gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"
decimalai skills add tetherto-qv-pr-reviewimport anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[anthropic]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
import anthropic
from decimalai.anthropic import agent_config
client = anthropic.Anthropic()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
question = "Hello!"
with support.run(question) as run: # one user message, one run
answer = client.beta.messages.tool_runner(
model="claude-haiku-5-5",
max_tokens=4096,
system=run.system, # your prompt, then the skill list
tools=[*run.tools], # your tools, then load_skill
messages=[{"role": "user", "content": question}],
).until_done()No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
import anthropic
client = anthropic.Anthropic()
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
reply = client.messages.create(
model="claude-haiku-5-5",
max_tokens=4096,
system=f"You are a helpful agent.\n\n{skill}",
messages=[{"role": "user", "content": "Hello!"}],
)This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"
decimalai skills add tetherto-qv-pr-reviewfrom google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[google-genai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
from google import genai
from google.genai import types
from decimalai.google_genai import agent_config
client = genai.Client()
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
def lookup_order(order_id: str) -> str:
return f"Order {order_id} shipped yesterday."
# Your functions, as declarations for the model, and what answers each.
TOOLS = [{"function_declarations": [{
"name": "lookup_order",
"description": "Look up the status of an order.",
"parameters_json_schema": {"type": "object", "properties": {"order_id": {"type": "string"}},
"required": ["order_id"]},
}]}]
FUNCTIONS = {"lookup_order": lookup_order}
question = "Where is order A1001?"
with support.run(question) as run: # one user message, one run
config = types.GenerateContentConfig(
system_instruction=run.system, # your prompt, then the skill list
tools=[*TOOLS, *run.tool_params], # your functions, then load_skill
)
contents = [{"role": "user", "parts": [{"text": run.message}]}]
while True:
response = client.models.generate_content(
model="gemini-3.5-flash", contents=contents, config=config,
)
if not response.function_calls:
break
contents.append(response.candidates[0].content)
parts = []
for call in response.function_calls:
# load_skill is answered by the run; your own functions by FUNCTIONS.
# A loop of your own rather than automatic function calling, which
# raises KeyError when the model asks for a function you did not pass.
function = FUNCTIONS.get(call.name)
parts.append(run.tool_result(call) or {"function_response": {
"name": call.name, "id": call.id,
"response": {"result": function(**(call.args or {})) if function
else f"There is no function named {call.name!r}."},
}})
contents.append({"role": "user", "parts": parts})
print(response.text)No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
response = client.models.generate_content(
model="gemini-3.5-flash",
contents="Hello!",
config=types.GenerateContentConfig(system_instruction=f"You are a helpful agent.\n\n{skill}"),
)This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openai
decimalai skills add tetherto-qv-pr-reviewfrom langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[langchain]" langchain-openaifrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
from langchain.agents import create_agent
from decimalai.langchain import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = create_agent(
"openai:gpt-5.4-mini",
tools=[], # your tools; the middleware adds load_skill
middleware=[support.middleware], # your prompt, the skill list and load_skill
)
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from langchain.agents import create_agent
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = create_agent("openai:gpt-5.4-mini", tools=[], system_prompt=f"You are a helpful agent.\n\n{skill}")
result = agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 1 other file is not included.
CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"
decimalai skills add tetherto-qv-pr-reviewimport decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()The same install from Python, for a notebook or a setup script. CrewAI's own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" "crewai>=1.15.27"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.crewai
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=decimalai.crewai.skills(), # the installed folder
tools=decimalai.crewai.skill_tools(), # reads a skill's files, runs its scripts
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()No DecimalAI package. CrewAI's own loader reads the folder, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from crewai import Agent, Crew, Task
agent = Agent(
role="Support", goal="Help the customer", backstory="A support agent.",
llm=llm, # your own CrewAI LLM
skills=[Path(".agents/skills")],
)
task = Task(description="Hello!", expected_output="an answer", agent=agent)
Crew(agents=[agent], tasks=[task]).kickoff()This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"
decimalai skills add tetherto-qv-pr-reviewfrom decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[adk]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.adk import DecimalAIPlugin, agent_config
from google.adk.agents import LlmAgent
from google.adk.apps import App
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = LlmAgent(
name="support", # ADK needs a Python identifier
model="gemini-3.5-flash",
instruction=support.instruction, # your prompt, then the skill list
tools=[*support.tools], # your tools, then load_skill
)
# DecimalAIPlugin makes each invocation of runners built from this App one trace.
app = App(name="support", root_agent=agent, plugins=[DecimalAIPlugin()])Prefer ADK's own loader? SkillToolset(skills=decimalai.adk.skills(), ...) reads the same folder. Use one or the other: a skill delivered both ways reaches the prompt twice.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from google.adk.agents import LlmAgent
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = LlmAgent(
name="support",
model="gemini-3.5-flash",
instruction=lambda context: f"You are a helpful agent.\n\n{skill}",
)This gets SKILL.md only. The skill's 1 other file is not included.
agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"
decimalai skills add tetherto-qv-pr-reviewfrom pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
The same install from Python, for a notebook or a setup script. agent_config() hands your agent the skill. With no DecimalAI key it reads your project's files and sends nothing. Needs SDK 0.27 or newer.
pip install "decimalai[pydantic-ai]"from decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
from pydantic_ai import Agent
from decimalai.pydantic_ai import agent_config
# With no DECIMAL_API_KEY this reads the skills your project lists, from disk.
support = agent_config("support", fallback="You are a helpful agent.")
agent = Agent(
"openai:gpt-4o-mini",
name=support.name,
tools=[], # your tools; the capability adds load_skill
capabilities=[support], # your prompt, the skill list and load_skill
)
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")On the Pydantic AI harness, decimalai.pydantic_ai_harness.skills(project) uses its own loader instead.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from pydantic_ai import Agent
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = Agent("openai:gpt-4o-mini", instructions=f"You are a helpful agent.\n\n{skill}")
# In a notebook or async code: result = await agent.run("Hello!")
result = agent.run_sync("Hello!")This gets SKILL.md only. The skill's 1 other file is not included.
Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagents
decimalai skills add tetherto-qv-pr-reviewimport decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
The same install from Python, for a notebook or a setup script. Deep Agents' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" deepagentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
import decimalai.deepagents
from deepagents import create_deep_agent
agent = create_deep_agent(model=model, **decimalai.deepagents.skills())
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})The default backend reads files but has no shell. To run a skill's scripts, pass skills(backend=LocalShellBackend(...)).
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from deepagents import create_deep_agent
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = create_deep_agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent.invoke({"messages": [{"role": "user", "content": "Hello!"}]})This gets SKILL.md only. The skill's 1 other file is not included.
Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-framework
decimalai skills add tetherto-qv-pr-reviewimport asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
The same install from Python, for a notebook or a setup script. Its own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" agent-frameworkfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
import asyncio
from agent_framework import Agent
from decimalai.agent_framework import skills
agent = Agent(client=client, instructions="You are a helpful agent.", context_providers=[skills()])
asyncio.run(agent.run("Hello!"))It caches skills by default, so restart the process after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdimport asyncio
from pathlib import Path
from agent_framework import Agent
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = Agent(client=client, instructions=f"You are a helpful agent.\n\n{skill}")
asyncio.run(agent.run("Hello!"))This gets SKILL.md only. The skill's 1 other file is not included.
Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agents
decimalai skills add tetherto-qv-pr-reviewfrom decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
The same install from Python, for a notebook or a setup script. Strands' own skill loader reads the installed folder. No DecimalAI account needed.
pip install "decimalai>=0.16" strands-agentsfrom decimalai import skillpkg
# Writes .agents/skills and .decimal/skills.toml. Safe to run twice.
result = skillpkg.add("tetherto-qv-pr-review")
if result.exit_code:
raise SystemExit(result.problems)
from decimalai.strands import skills
from strands import Agent
from strands.vended_tools import file_editor, shell
agent = Agent(
model=model, # always pass one: the default is Amazon Bedrock
system_prompt="You are a helpful agent.",
tools=[file_editor, shell],
plugins=[skills()],
)
agent("Hello!")Strands loads an agent's skills once, so build the agent again after decimalai skills update.
No DecimalAI package. The whole skill goes in every prompt, and nothing pins or updates it.
.agents/skills/tetherto-qv-pr-review/SKILL.mdfrom pathlib import Path
from strands import Agent
skill = Path(".agents/skills/tetherto-qv-pr-review/SKILL.md").read_text()
agent = Agent(model=model, system_prompt=f"You are a helpful agent.\n\n{skill}")
agent("Hello!")This gets SKILL.md only. The skill's 1 other file is not included.
Adds every file the skill has, pinned so you can update it. Cursor, Copilot, Cline and Warp read this folder by themselves.
pip install "decimalai>=0.15"
decimalai skills add tetherto-qv-pr-reviewNo Python or account. Cursor, Copilot, Cline and Warp read this folder by themselves.
curl -fsSL --create-dirs -o .agents/skills/tetherto-qv-pr-review/SKILL.md https://app.decimal.ai/s/tetherto-qv-pr-review/SKILL.md.agents/skills/tetherto-qv-pr-review/SKILL.mdThis gets SKILL.md only. The skill's 1 other file is not included.
Give its text to your model as instructions. No account or package needed.
This gets SKILL.md only. The skill's 1 other file is not included.
Raw files: /s/tetherto-qv-pr-review/SKILL.md · /s/tetherto-qv-pr-review.json
Fetch the file and give its text to your model as instructions. No account or package needed.
curl -fsSL https://app.decimal.ai/s/tetherto-qv-pr-review/SKILL.mdcurl -fsSL https://app.decimal.ai/s/tetherto-qv-pr-review.jsonThis gets SKILL.md only. The skill's 1 other file is not included.
You don't need a DecimalAI account.
Saved prompts and skills, pinned versions, and traces of this skill in your runs. DecimalAI does not run your agent.
Claude Code reads files from your project, so DecimalAI config does not apply there.
On Claude Agent SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI Agents: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On OpenAI SDK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Anthropic: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Gemini API: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On LangChain: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On CrewAI: set DECIMAL_API_KEY and add decimalai.init(crewai=True) to record runs and skill use. The skill version stays in your repo.
On Google ADK: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Pydantic AI: save the skill to an agent here, set DECIMAL_API_KEY, and the same code uses it with no command to run. Changes you save reach the next run with no redeploy.
On Deep Agents: set DECIMAL_API_KEY and add decimalai.init(langchain=True) to record runs and skill use. The skill version stays in your repo.
On Microsoft Agent Framework: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
On Strands: set DECIMAL_API_KEY and add decimalai.init(otel=True, agent_name="support") to record runs and skill use. The skill version stays in your repo.
Editors read files from your project, so DecimalAI config does not apply there.
Anywhere else: record a use with decimalai.log_skill_activation() inside a trace.
Manual-trigger PR review for any GitHub PR in the configured repository. Produces:
The user submits the pending review manually from the GitHub UI.
Use when:
/qv-pr-review/qv-sdk-pr-status, /qv-pr-mine, or another pod's status/my skillThe skill applies to any PR; cursor rules and PR-template format applicable to the touched paths are discovered dynamically (see step 4 / step 5).
https://github.com/tetherto/qvac/pull/1234)If PR URL is missing, ask for it. Nothing else to ask unless the user's seed notes are ambiguous.
Carefully check the changes, focusing on:
Style nits, doc polish, and unverified hunches are NOT the focus. Don't pad the review with them.
Use these tiers when assembling findings. Tiers drive what surfaces in the chat overview and what is proposed for inline comments. The user always has final say over what gets posted.
Selection rule: never silently include a Low finding in the inline payload. The user picks (see step 7b).
This skill is read-only with respect to the user's local working tree. The user may have uncommitted changes, be on a feature branch, or have pending work — never disturb it.
Forbidden commands (no matter the circumstance):
git switch, git checkout (any ref/file)git reset (any mode), git restoregit stash (push, pop, drop, anything)git pull, git merge, git rebase, git cherry-pickgit cleangh pr checkout/qv-pr-review runs by default in worktree mode (see step 0a). The dedicated cache directory at ~/.cache/qvac-pr-review/ is fully isolated from the user's working tree — it lives outside the repo entirely. Inside that cache directory only, the shared script (worktree-prepare.mjs) is allowed to:
git fetch <remote> "pull/<n>/head:refs/pr/<n>/head"git worktree add --detach <cache-path> refs/pr/<n>/headgit -C <cache-path> reset --hard refs/pr/<n>/head (when SHA drifted)git -C <cache-path> reset --hard HEAD (when the cached worktree is dirty at the same SHA)git -C <cache-path> clean -fdx (only after SHA drift, to evict stale untracked build/test artifacts)git worktree remove --force <cache-path> and git worktree prunegit -C <cache-path> rev-parse|log|show|diff|statusThese run from the script, not from the agent. The agent itself MUST NOT run any of the forbidden commands above — including inside the cache path. The agent only Reads/Greps/Globs source files in the cache path and never writes to them during /qv-pr-review.
The same cache path is also used by /qv-pr-test, so it may contain untracked build/test artifacts such as node_modules, dist, native build/ directories, or logs. Those artifacts are ignored by /qv-pr-review; the patch is computed from committed refs (<BASE_REF>...HEAD), not from the worktree's unstaged or untracked state.
If you need PR file contents:
<cache-path>/...). The path is at the PR head SHA.--no-worktree): gh api repos/{owner}/{repo}/contents/{path}?ref={sha} and write to /tmp/.Read cursor rules and repo conventions from the user's current workspace as-is — do not switch branches to "get the latest" version.
Every shell call costs a user approval. Keep the total small (~5-8 calls).
cat/head/tail, Grep instead of grep/rg, Glob instead of find, Write instead of echo > / heredoc.gh pr checkout. Use worktree mode (default, see step 0a) for full local context at the PR head SHA. The cache lives under ~/.cache/qvac-pr-review/ and never touches the user's working tree./tmp/pr-<num>.json and /tmp/pr-<num>.patch, reuse via Read/Grep. The worktree path is reused across step calls; don't re-prepare it.gh pr checks — statusCheckRollup in gh pr view --json already has every check.gh run view --log-failed --job <id> per failing job.file, od, wc -c, cat -A) unless a CI log explicitly names an encoding issue.Copy this checklist and track progress:
- [ ] 0a. Prepare worktree (default-on; skip if user passed --no-worktree)
- [ ] 1. Parse PR URL
- [ ] 2. Fetch PR data (2 shell calls)
- [ ] 3. Validate gitflow
- [ ] 4. Read applicable cursor rules for the touched paths
- [ ] 5. Validate PR title + body against the discovered format rules
- [ ] 6. Review: CI + general + security + rules — classify findings by severity
- [ ] 6b. Apply SDK plugin checklist (only if PR touches plugin paths)
- [ ] 7a. Print risk overview in chat (high + medium + material lows)
- [ ] 7b. Ask user which findings to include as inline comments (high+medium pre-selected, lows opt-in)
- [ ] 8. Assemble inline comments + write payload (only the user-confirmed set)
- [ ] 9. Pre-flight check (count, files, line numbers)
- [ ] 10. Show gh api command, wait for user confirmation
- [ ] 11. POST the PENDING review
- [ ] 12. Output link to pending review
Worktree mode is the default — full local Read/Grep/Glob context at the PR head SHA, isolated under ~/.cache/qvac-pr-review/, never touches the user's working tree. The same script also fetches the PR's base ref and writes the canonical PR diff to /tmp/. Skip this step only if the user invoked /qv-pr-review URL --no-worktree.
node .cursor/skills/_lib/pr-skills/worktree-prepare.mjs <PR-URL>
Parse the script's output:
stdout on success has four lines:
WORKTREE_PATH=<absolute path>
HEAD_SHA=<sha>
PATCH_PATH=/tmp/pr-<num>.patch
BASE_REF=<remote>/<baseRefName>
WORKTREE_PATH: the working root for files at the PR head SHA. Use this for all Read/Grep/Glob in steps 6 and 7a.PATCH_PATH: a unified diff computed locally with git diff <BASE_REF>...HEAD (3-dot). 3-dot semantics match GitHub's PR view exactly — only what the PR introduces, regardless of how far behind the base the PR is. Use this anywhere the workflow refers to the patch; do NOT use 2-dot.BASE_REF: the local tracking ref the diff was computed against (e.g. upstream/main). Useful if you need to re-run a custom diff inside the worktree.stderr on failure has a single line:
WORKTREE_FALLBACK=<one-line reason>
The script's exit code is 0 even on failure. If you observe WORKTREE_FALLBACK, fall back to the API-only flow: fetch file contents via gh api repos/{owner}/{repo}/contents/{path}?ref={headRefOid} and the patch via gh pr diff <num> --patch > /tmp/pr-<num>.patch. Surface the fallback reason once in the chat overview's ### Verified (no action) section so the user knows local context is missing — e.g. "Worktree prep failed (<reason>); excerpts come from gh api."
When the user passes --no-worktree, skip this step entirely and use the API-only flow without surfacing any fallback note.
Extract owner, repo, pr_number from the URL. If ~/.config/qvac-pr-skills/config.json exists, verify the PR repo matches github.repo; otherwise use the repo in the provided PR URL.
gh pr view <num> --repo tetherto/qvac \
--json number,title,state,mergeable,baseRefName,headRefName,headRefOid,isCrossRepository,headRepositoryOwner,files,author,body,statusCheckRollup \
> /tmp/pr-<num>.json
In worktree mode (default), the patch is already at /tmp/pr-<num>.patch from step 0a — do NOT re-fetch it via gh pr diff.
In --no-worktree mode (or after a WORKTREE_FALLBACK), additionally:
gh pr diff <num> --repo tetherto/qvac --patch > /tmp/pr-<num>.patch
Everything else comes from these files via Read/Grep. No additional shell calls for PR data.
Read baseRefName, headRefName, isCrossRepository, headRepositoryOwner from /tmp/pr-<num>.json. Full gitflow rules are in docs/gitflow.md.
Allowed directions (fork to upstream):
| Head (fork branch) | Base (upstream) | OK? |
|---|---|---|
| anything | main | yes |
| anything | release-<pkg>-<x.y.z> | yes (must bump version + changelog) |
| anything | feature-<pkg>-* / tmp-<pkg>-* | yes |
Blocker patterns:
release-* to main — WRONGmain to release-* — WRONGrelease-* to release-* — WRONGfeature-* / tmp-* to main — WRONGmain to feature-* / tmp-* — WRONGRelease-PR extra checks (base is release-<pkg>-<x.y.z>):
packages/<pkg>/package.json version must increase vs basepackages/<pkg>/CHANGELOG.md must be updatedUse Glob/Read tools (no shell). The repo organizes cursor rules per area under .cursor/rules/<area>/. To pick which apply to this PR:
/tmp/pr-<num>.json (files[].path)..cursor/rules/**/*.mdc and look at each file's frontmatter globs field. Load any rule whose globs match at least one touched path. (Many rules also have alwaysApply: true and apply regardless.).github/teams/<pod>.json exists for a pod whose ownedPaths match the touched files, that pod's conventions are the relevant ones; load any rule under .cursor/rules/<pod>/ (e.g. main.mdc, commit-and-pr-format.mdc, error-handling.mdc, <pod>-pod-packages.mdc).If no rules match, skip this step. Do not hardcode rule paths.
If a commit-and-pr-format.mdc-style rule was loaded in step 4, validate the PR title and body against it. Common shape (used by the SDK pod and likely others):
Title (format: TICKET prefix[tag]: subject or prefix[notask]: subject):
feat fix doc test chore infra[api] [bc] [mod] [notask] [skiplog][api] required when diff adds new exports/public API surface[bc] required when diff removes/changes existing public API signatures[mod] required when model constants changeBody — use the matching .github/PULL_REQUEST_TEMPLATE/<template>.md if one is referenced by the loaded rule:
[bc] requires BEFORE/AFTER code blocks[api] requires usage example[mod] requires Added/Removed models listIf no format rule applies, skip this step. Title/body violations go in the chat overview only, not as inline PR comments.
Apply the review philosophy. Classify every finding as High, Medium, or Low. Skip any dimension with no findings.
*Approval*/approval-worker). Name the failing job + actual error. For failing jobs only: gh run view --repo tetherto/qvac --log-failed --job <job_id> > /tmp/pr-<num>-<job_id>.log (High)When verifying a suspected bug, attempt to construct a concrete reproduction (input → code path → observed behavior). If you cannot, classify it Medium at most and say so.
If the PR touches SDK plugin paths, also run the SDK plugin integration checklist. Trigger when any touched path (files[].path in /tmp/pr-<num>.json) matches:
packages/sdk/server/bare/plugins/**packages/sdk/schemas/plugin.ts or packages/sdk/schemas/load-model.tspackages/sdk/schemas/*-config.tspackages/sdk/server/worker.tspackages/sdk/commands/bundle/**If no path matches, skip this step entirely — no checklist output. When it triggers, read references/sdk-plugin-checklist.md and follow its "How to apply" and "Output integration" sections. Real blocking gaps are classified by severity here in step 6 and flow into the normal inline-comment selection (step 7b); the cross-cutting summary renders as the ### SDK plugin checklist block in the step 7a overview.
Print the overview below directly in chat. This is for the user — nothing is posted yet. After printing, pause for the selection step (7b). If the user pushes back on a finding, drop it before continuing.
Important: the user's local checkout may be on a different branch / different commit than the PR head. They cannot trust line numbers from their working tree. Every High/Medium finding MUST therefore include:
A short code excerpt fetched from the PR head SHA (already in /tmp/pr-<num>-<file>.ts from step 6 verification, or via gh api repos/{owner}/{repo}/contents/{path}?ref={headRefOid}). 3-8 lines of context max — just enough to make the bug visible without the user opening the PR.
A deep link to the PR file diff so the user can click straight to the right place on GitHub. GitHub PR file anchors use SHA256 of the file path (GitHub switched from MD5/SHA1 in 2022):
https://github.com/tetherto/qvac/pull/<num>/files#diff-<sha256(path)>R<line>
Where <sha256(path)> is sha256(<path>) (lowercase hex, no trailing newline). The R<line> suffix anchors the right (post-change) side at that line; use L<line> for the left side.
Compute it in shell:
printf '%s' 'packages/sdk/foo.ts' | shasum -a 256 | awk '{print $1}'
Fallback when SHA256 isn't convenient: link to the blob at the head SHA — https://github.com/tetherto/qvac/blob/<headRefOid>/<path>#L<line> — also clickable and lands on the right line, but doesn't show the diff context. Prefer the diff anchor when you have it.
Do NOT use SHA1 of the path — that produces a hash GitHub no longer recognises and the anchor will silently fail to scroll.
Format:
## PR #<num> — review overview
<1-line summary of what this PR does>
### Gitflow / Title / CI
<one-line status, omit subsections that are clean>
### High-risk
<numbered list — empty list is fine, write "none" explicitly>
1. **<short title>** — [`<path>:<line>`](<deep link>)
<one-sentence explanation, with repro hint if relevant>
```ts
<3-8 line excerpt from the PR head>
```
### Medium-risk
<numbered list — empty list is fine, write "none" explicitly>
1. **<short title>** — [`<path>:<line>`](<deep link>)
<one-sentence explanation>
```ts
<3-8 line excerpt from the PR head>
```
### Low-risk (informational)
<numbered list — omit the section entirely if there are no material lows>
1. **<short title>** — [`<path>:<line>`](<deep link>)
<one-sentence note>
### Verified (no action)
<optional: short bullets for things you specifically checked and cleared, only if the reviewer might otherwise wonder>
### SDK plugin checklist
<only when step 6b triggered AND has gaps — omit entirely otherwise. Format per references/sdk-plugin-checklist.md "Output integration".>
---
PR diff: <https://github.com/tetherto/qvac/pull/<num>/files>
Rules for what goes in each section:
The excerpts MUST come from files at the PR head SHA, never from the user's working tree.
<WORKTREE_PATH>/<path> (the worktree is checked out at the PR head SHA). Glob/Grep with the worktree path as the search root.--no-worktree: fetch with gh api repos/{owner}/{repo}/contents/{path}?ref=<headRefOid> (decode .content from base64) and Read from /tmp/.The excerpt's line numbers must match the line you're calling out.
Immediately after the overview, present a confirmation prompt. The defaults follow the severity tier rules (high+medium pre-selected, lows opt-in). The user can override any selection.
Use a structured multi-select question (one per finding) so the user clicks instead of typing. Format the prompt as:
For each finding, confirm whether it should be posted as an inline comment.
Defaults: High + Medium = include; Low = skip.
Each finding becomes one option in a single multi-select question (id inline_picks, allow_multiple: true). Pre-select High + Medium by listing them as the recommended choices in the prompt text (the tool itself doesn't surface defaults, so spell them out: e.g. "Recommended: 1, 2, 3"). Lows are listed as additional options.
If the user is text-driven instead of clicking, accept replies like:
Echo back the final selected list before moving to step 8 so the user can object once more. Do NOT proceed to step 8 until you have an explicit confirmation. If the user picks "none", skip steps 8-12 and end the session.
blocker:, nit:, etc.) inside the comment body — severity is conveyed by which findings make it into the chat overview.Build the payload from the user-confirmed selection in step 7b only. Never include a finding the user did not opt into (especially Lows). If the confirmed set is empty, stop here — don't post an empty review.
Use the Write tool to create /tmp/pr-<num>-review.json:
{
"commit_id": "<headRefOid>",
"comments": [
{
"path": "packages/<pkg>/src/foo.ts",
"line": 42,
"body": "comment text"
}
]
}
Omit the event field. The GitHub REST API only accepts APPROVE, REQUEST_CHANGES, or COMMENT; omitting it leaves the review in PENDING state, which is what this skill targets.
Line numbers must reference the post-PR file line numbers (the + side line numbers in the patch, mapped to the file at the PR head SHA). When in doubt, fetch the file at the head SHA via gh api repos/{owner}/{repo}/contents/{path}?ref={sha} and verify line numbers there before composing the payload.
Show the user:
Count of inline comments and which files they touch. Cross-reference against the user-confirmed selection from step 7b — counts MUST match exactly. If they don't, something was added or dropped silently; stop and reconcile before proceeding.
A rendered Markdown preview of every inline comment, one per file:line anchor. Reproduce the comment body verbatim as Markdown — do NOT show the raw JSON payload, do NOT escape newlines. The user reads the preview to decide whether to approve posting; raw JSON is unreadable. Format:
### Preview — comment <n> of <total>
**File**: `<path>:<line>`
---
<verbatim comment body, rendered as Markdown>
---
If the comment body contains fenced code blocks, render them as fenced code blocks in the preview (not as escaped strings).
Show the exact gh api command but do NOT run it until the user says to proceed:
gh api repos/tetherto/qvac/pulls/<num>/reviews \
--method POST \
--input /tmp/pr-<num>-review.json
Run the command. If it fails, show the error and the JSON payload for debugging.
https://github.com/tetherto/qvac/pull/<num>#pullrequestreview-<review_id>
references/sdk-plugin-checklist.md.github/teams/<pod>.json.cursor/rules/<pod>/.github/PULL_REQUEST_TEMPLATE/docs/gitflow.mdOnly when you call it by name. Your agent never loads it on its own. Use when reviewing a PR, given a PR link, or invoking /qv-pr-review.
Skills that do similar work, with their headline benchmark lift.
From the same source and nearby in the catalogue.