Add a Custom Review Lens (BYO Skills)¶
A lens is one focused review question for the model. lgtmaybe ships eleven built-in review lenses:
- security, correctness, deprecation, tests, documentation, performance, complexity, intent and spec;
ponytail, the "lazy senior dev", write-less-code lens;architecture, for design decisions that will be expensive to reverse.
A custom lens adds your own. It runs alongside the built-ins as its own focused model call, and its findings merge into the same review. Use one to enforce a house style or team convention the built-in lenses don't cover.
A custom lens can't reuse a built-in id. So a lens you named architecture
before it became built-in needs a new id.
Contents¶
- How a Lens Works
- Inline in
.lgtmaybe.yml - As Reusable Skill Files
- Bundled Lens Packs (
pack:<name>) - Run It
- Security: Lenses Are Trusted Input
- See Also
How a Lens Works¶
Every lens, built-in or custom, has the same shape: a focused set of
instructions and one worked example. Each custom lens is sent as its own model
call, so the model concentrates on a single concern. (Under the default fast
preset, the built-in lenses share five calls,
plus a sixth when a committed spec matches.
Under full, each built-in gets its own call.) A custom lens has these
fields:
| Field | Required | What it is |
|---|---|---|
id |
yes | A unique short name. It can't be a built-in category name or start with scan:. |
instructions |
yes | What to look for, in plain language. This is the lens. |
title |
no | A human-readable heading (defaults to id). |
example_diff + example_finding |
no (together) | A worked example: a hunk and the finding the model should return for it. Without one, the lens gets a generic example. Supplying your own noticeably improves smaller models. |
Findings from the lens carry its id as their category. So
finding_rules can drop or re-grade
them like any built-in lens.
Inline in .lgtmaybe.yml¶
The quickest way is to define the lens in your repo config:
provider: ollama
model: qwen3.6:27b
extra_lenses:
- id: house-style
title: House style
instructions: |
Enforce our team conventions on changed code. Flag new public functions
that return bare dicts instead of a typed dataclass, and any logging at
WARNING or above that doesn't include a correlation id.
example_diff: |
--- a/api.py
+++ b/api.py
@@ -10,1 +10,2 @@
def make_user(name):
+ return {"name": name, "active": True}
example_finding:
path: api.py
line: 11
severity: low
title: Public function returns a bare dict
body: House style returns a typed dataclass from public functions, not a dict.
suggestion: " return User(name=name, active=True)"
The Ponytail lens is built in
The "lazy senior dev / write less code" instinct from
Ponytail ships as the built-in
ponytail lens, so you don't need a custom lens for it. Use extra_lenses
for something the built-ins don't cover, like the house-style rule above.
Custom lenses share the never-cut rule
Every lens, custom and bundled ones included, runs under one shared rule. No lens may recommend removing trust-boundary validation, data-loss error handling, security controls, or accessibility support.
Custom lenses sit outside the deterministic conflict backstop. That backstop only arbitrates lens pairs lgtmaybe declares opposed. So write instructions that don't contradict the built-ins. See When Lenses Disagree.
As Reusable Skill Files¶
You may want to share lenses across repos, or let an agent harness add its own.
Put each lens in its own file, and point lens_paths at the file or its
directory:
# .lgtmaybe.yml
provider: ollama
model: qwen3.6:27b
lens_paths:
- .lgtmaybe/skills
# .lgtmaybe/skills/house-style.yml
id: house-style
title: House style
instructions: |
Flag new public functions that return bare dicts instead of a typed dataclass,
and WARNING+ logging without a correlation id.
A skill file holds one lens (a mapping) or several (a list). A directory entry
loads every *.yml and *.yaml file in it. Paths resolve relative to where
lgtmaybe runs, normally the repo root.
Lenses from lens_paths are appended to any inline extra_lenses. Every id
must be unique across the whole set.
Bundled Lens Packs (pack:<name>)¶
lgtmaybe ships an opt-in library of extra lenses, called lens packs. They are distilled from the wider engineering-review ecosystem: Ousterhout, Metz, Carmack, the NASA Power of Ten / TigerStyle, Google's eng-practices, and several skill and rule collections. See ATTRIBUTION.md.
The packs are off by default for two reasons. They are more opinionated than the built-ins. And every lens you enable adds a model call per batch.
Enable a pack by name with the pack: scheme in lens_paths. This works for a
pip install, which has no repo-relative path to point at:
# .lgtmaybe.yml
provider: ollama
model: qwen3.6:27b
lens_paths:
- pack:design # combine as many as you like
- pack:robustness
| Pack | Lenses | Use it when |
|---|---|---|
pack:design |
wrong-abstraction, shallow-module, information-leakage, errors-out-of-existence, hidden-state, naming |
You want review of structure and taste at a finer grain than the built-in architecture lens: the right abstraction, deep modules, explicit state. |
pack:robustness |
assertions, bounded, idempotency, migrations, portability, observability |
Operational safety: defensive invariants, bounded work, retry-safe side effects, safe migrations. |
pack:interface |
api-design, type-safety, magic-values, comment-why |
Contracts and clarity: backward compatibility, tight types, named constants, comments that explain why. |
pack:frontend |
accessibility, i18n |
The diff touches UI or user-facing copy. |
Each pack is a directory of skill files inside the package. You can browse or
copy any single lens from
src/lgtmaybe/lenses/.
An unknown pack: name fails with a list of the packs that exist.
Run It¶
Once custom lenses are in your config, they run on every review, CLI or GitHub Action. On the CLI, their findings appear alongside the built-ins:
lgtmaybe review --provider ollama --model qwen3.6:27b --api-base http://localhost:11434
categories controls only the built-in lenses. You can narrow it and your
custom lenses still run.
Security: Lenses Are Trusted Input¶
A lens's instructions and example reach the model as trusted reviewer instructions. So treat a lens like code you run:
- Define lenses only in files you control, such as your committed
.lgtmaybe.ymlor repo skill files. - Never source them from a pull request.
On pull_request_target, the Action reads config from the base branch, not the
PR head. So a fork PR can't add or change a lens. The diff itself is always
treated as untrusted data, separately from your lenses.
See Also¶
- Configure .lgtmaybe.yml - the field reference.
- What gets reviewed - the built-in lenses.