Create custom Tessl reviewer plugins – fork the default rubric, build one from scratch, or derive its rubrics from evidence (existing skills, PR review feedback, agent logs). Scaffolds the plugin directory structure, authors rubrics and config.json, and validates the result with tessl review run.
96
93%
Does it follow best practices?
Impact
99%
1.37xAverage score across 6 eval scenarios
Advisory
Suggest reviewing before use
tessl review scores a skill against a reviewer plugin. With no --review-plugin, it uses Tessl's default rubric (Anthropic best practices). A custom reviewer plugin lets you change what "good" means - re-weight dimensions, edit the scoring anchors, or add your own judges - then gate CI on your team's standard.
This skill creates that plugin. There are two ways to start:
tessl/default-skill-review, fetched on demand — and tweak it. Best when you mostly agree with the default and want to adjust weights, wording, or add a dimension.Both produce the same plugin structure and are validated the same way.
Tessl's default rubric is published as the plugin tessl/default-skill-review — the exact rubric tessl review uses out of the box. Step 2 fetches it so you can read it before deciding whether to fork or start fresh; this skill no longer bundles its own copy, so what you fork is always the current default.
It scores three components (weights live in config.json):
| Component | Weight | What it measures |
|---|---|---|
validation | 0.2 | Deterministic checks: frontmatter, line count, schema, licence |
description judge | 0.4 | How well the skill's description drives activation |
content judge | 0.4 | Quality of the SKILL.md body |
The two judges and their dimensions (all on a 1-3 scale):
description.json - specificity (0.2), trigger term quality (0.3), completeness (0.35), distinctiveness / conflict risk (0.15)content.json - conciseness (0.3), actionability (0.3), workflow clarity (0.25), progressive disclosure (0.15)To fork the default, copy these files into your plugin and edit them (Step 4, Path A).
Every reviewer plugin has this layout:
<plugin-name>/
├── .tessl-plugin/
│ └── plugin.json
└── skills/skill-reviewer/
├── SKILL.md
└── references/
├── config.json
├── schemas/
│ ├── rubric.schema.json
│ ├── config.schema.json
│ └── results.schema.json
└── rubrics/
└── <judge-name>.json (one per judge)The schema files come from the fetched default reviewer (Step 2 copies them in). Use them verbatim — do not modify them.
Before writing files, decide:
description, content).evaluation_target) and what weight it carries in the final score.validation_weight + Σ(judges[stem].weight) = 1.0validation_weight controls how much the deterministic validation pass (frontmatter checks, line count, etc.) contributes to the score. Set to 0.0 to exclude validation from scoring entirely (it still runs as a gate).
Examples:
| Setup | config.json |
|---|---|
| Default | validation_weight: 0.2, judges: { description: { weight: 0.4 }, content: { weight: 0.4 } } |
| 1 judge, validation off | validation_weight: 0.0, judges: { content: { weight: 1.0 } } |
| 3 judges | validation_weight: 0.1, judges: { security: { weight: 0.5 }, clarity: { weight: 0.3 }, brevity: { weight: 0.1 } } |
The judge key in config.json is the rubric filename stem — security.json → key security.
Ask the user where to create the plugin (or use ./my-reviewer). Then create the directory structure:
PLUGIN_DIR=<chosen-path>
mkdir -p "$PLUGIN_DIR"/{.tessl-plugin,skills/skill-reviewer/references/{rubrics,schemas}}Fetch the published default reviewer (tessl/default-skill-review) into a temp dir — no install, it does not touch your project. Both starting points scaffold from it:
DEFAULT_DIR=$(mktemp -d)
curl -fsSL "https://codeload.github.com/tesslio/product-plugins/tar.gz/refs/heads/main" \
| tar -xz -C "$DEFAULT_DIR" --strip-components=2 "product-plugins-main/default-skill-review"
DEFAULT_SRC="$DEFAULT_DIR/skills/skill-reviewer"Copy the canonical schema files and the reviewer agent prompt from the fetched default:
cp "$DEFAULT_SRC"/references/schemas/*.json \
"$PLUGIN_DIR/skills/skill-reviewer/references/schemas/"
cp "$DEFAULT_SRC"/SKILL.md \
"$PLUGIN_DIR/skills/skill-reviewer/SKILL.md"The plugin's SKILL.md is the reviewer agent instruction — it drives the agent during review runs. Start from the fetched default verbatim and make only targeted edits.
config.json/rubrics/ and write results.json — these are load-bearing for scoringThe results.json output schema is enforced at the recipe level regardless of what your SKILL.md says — the agent's output is validated against the schema even if the instruction differs.
plugin.jsoncat > "$PLUGIN_DIR/.tessl-plugin/plugin.json" << 'EOF'
{
"name": "<workspace>/<plugin-name>",
"version": "0.1.0",
"description": "<what this plugin evaluates>",
"private": true,
"skills": "./skills/"
}
EOFThe default is already fetched in Step 2 ($DEFAULT_SRC). Copy its rubrics and config into your plugin, then edit them:
cp "$DEFAULT_SRC"/references/rubrics/description.json \
"$PLUGIN_DIR/skills/skill-reviewer/references/rubrics/"
cp "$DEFAULT_SRC"/references/rubrics/content.json \
"$PLUGIN_DIR/skills/skill-reviewer/references/rubrics/"
cp "$DEFAULT_SRC"/references/config.json \
"$PLUGIN_DIR/skills/skill-reviewer/references/config.json"Now make targeted edits. Common tweaks:
conciseness up). The rubric's dimension weights must still sum to 1.0.config.json (e.g. weight content over description). validation_weight + judge weights must still sum to 1.0.content), then re-balance that rubric's dimension weights to sum to 1.0.<stem>.json in rubrics/ and add <stem>: { weight } to config.json.Forking already wrote config.json, so edit it in place rather than writing a new one in Step 5. Go to Step 6 to validate.
For each judge, create $PLUGIN_DIR/skills/skill-reviewer/references/rubrics/<stem>.json.
A rubric file must conform to references/schemas/rubric.schema.json (copied into your plugin in Step 2). Key fields:
evaluation_target — what is being evaluated (matches the judge's purpose)scale — { "min": 1, "max": 3 } is standardreference_examples — judging_guidelines (array of strings), good_overall_examples, bad_overall_examplesdimensions — array of scoring dimensions; weights within a rubric must sum to 1.0Each dimension needs: id (snake_case), name, weight, question, and scores (array with score, anchor, example for each level from min to max).
Minimal rubric template:
{
"$schema": "../schemas/rubric.schema.json",
"evaluation_target": "<what-this-judge-evaluates>",
"scale": { "min": 1, "max": 3 },
"reference_examples": {
"judging_guidelines": [
"Award 3 only when <specific criterion>.",
"Score 1 for <negative criterion>."
],
"good_overall_examples": ["<example of a high-scoring skill>"],
"bad_overall_examples": ["<example of a low-scoring skill>"]
},
"dimensions": [
{
"id": "dimension_one",
"name": "Dimension One",
"weight": 0.6,
"question": "Does the skill <specific question>?",
"scores": [
{ "score": 1, "anchor": "No evidence of <criterion>", "example": "<bad example>" },
{ "score": 2, "anchor": "Partial <criterion>", "example": "<ok example>" },
{ "score": 3, "anchor": "Clear and complete <criterion>", "example": "<good example>" }
]
},
{
"id": "dimension_two",
"name": "Dimension Two",
"weight": 0.4,
"question": "Does the skill <second question>?",
"scores": [
{ "score": 1, "anchor": "...", "example": "..." },
{ "score": 2, "anchor": "...", "example": "..." },
{ "score": 3, "anchor": "...", "example": "..." }
]
}
]
}Dimension weight values within a single rubric must sum to 1.0 (separate from the plugin-level judge weights in config.json).
config.json (scratch path)Path A already copied a config.json — edit that one instead. For Path B, write it:
cat > "$PLUGIN_DIR/skills/skill-reviewer/references/config.json" << 'EOF'
{
"$schema": "../schemas/config.schema.json",
"validation_weight": <value>,
"judges": {
"<rubric-stem-1>": { "weight": <value> },
"<rubric-stem-2>": { "weight": <value> }
}
}
EOFVerify the invariant: validation_weight + sum of all judge weights = 1.0.
Create a throwaway skill to test against:
SKILL_DIR=$(mktemp -d)
cat > "$SKILL_DIR/SKILL.md" << 'EOF'
---
name: plugin-test
description: Use when testing a custom reviewer plugin.
---
# Plugin test
Use when verifying a custom reviewer plugin works correctly.
EOFRun with your new plugin:
tessl review run "$SKILL_DIR" \
--workspace <workspace-name> \
--review-plugin "$PLUGIN_DIR" \
--force \
--jsonHealthy output — the JSON should contain:
validation.overallPassed: truejudges map with one key per rubric stemreview.reviewScore as an integer 0–100If the plugin fails validation (bad weights, missing rubric, malformed schema), the API returns 400 with a message describing the exact problem. Fix the relevant file and re-run.
| Problem | Cause | Fix |
|---|---|---|
| 400 "weights do not sum to 1.0" | validation_weight + judge weights ≠ 1.0 | Adjust values in config.json |
| 400 "judges key X does not match any rubric" | Key in config.json has no matching X.json in rubrics/ | Create the rubric or fix the key name |
| 400 "rubric dimensions weights" | Dimension weights in a rubric don't sum to 1.0 | Fix the dimensions[].weight values in that rubric |
judges map empty in results | SKILL.md is missing the rubric-reading steps | Ensure SKILL.md includes the steps that read config.json/rubrics/ and write results.json |
| Score always 0 | validation_weight: 0 AND judge scoring all returned 0 | Check rubric anchors and examples |