Import 13 skills from the old opencode setup into turnstone

skills/ carries the SKILL.md files; scripts/import_skills.py runs
inside the VM via turnstone's own storage layer (SKILL.md parser +
risk scanner) so rows match what the console admin API would write.
Server must be restarted after import (stale WAL snapshot).
This commit is contained in:
marvin committed 2026-09-28 20:24:47 +02:00
1 parent 93fa05bdcc
commit 18fdff7d8c
15 files changed
+1973

No files matched your search

+19
View File
@@ -122,6 +122,25 @@ python3 scripts/persona.py # --db data/turnstone.db default
(Without a console deployment, persona CRUD is a direct SQLite write;
with `turnstone-console` running, use its admin API/Personas tab.)
### Skills
The 13 skills from the old opencode setup (`~/marvin/configs/opencode/skills`,
copied into this repo under `skills/`) are imported into turnstone's
DB-backed catalog via `scripts/import_skills.py`. The script runs
INSIDE the VM (uses turnstone's storage layer + SKILL.md parser +
risk scanner):
```sh
scp -O -i configs/ssh/id_ed25519 -P 2222 -r skills scripts/import_skills.py \
marvin@localhost:/tmp/
ssh -i configs/ssh/id_ed25519 -p 2222 marvin@localhost \
/opt/turnstone/bin/python /tmp/import_skills.py --dir /tmp/skills
```
Idempotent (`--update` re-imports + appends version rows). **Reboot the
VM after importing** — the running server holds a stale WAL snapshot
and won't see new rows until restart.
## Notes / trade-offs
- boco boots with `-snapshot`: **only the virtiofs shares persist.**
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
"""Import SKILL.md skill directories into the turnstone DB.
Runs INSIDE the VM (needs the turnstone venv), using turnstone's own
storage layer and SKILL.md parser so rows match what the console admin
API / in-session `skills` tool would have written (risk scan included):
/opt/turnstone/bin/python import_skills.py --dir /tmp/skills
The DB (data/turnstone.db on the host) is shared into the guest at
/var/lib/turnstone/turnstone.db. Idempotent: existing skill names are
skipped unless --update (which re-imports and appends a version row).
"""
from __future__ import annotations
import argparse
import json
import pathlib
import uuid
from turnstone.core.skill_parser import parse_skill_md
from turnstone.core.storage._sqlite import SQLiteBackend
DB_DEFAULT = "/var/lib/turnstone/turnstone.db"
def next_version(backend: SQLiteBackend, skill_id: str) -> int:
try:
rows = backend.list_skill_versions(skill_id)
return max((int(r["version"]) for r in rows), default=0) + 1
except Exception: # noqa: BLE001
return 1
def import_one(
backend: SQLiteBackend, skill_dir: pathlib.Path, update: bool
) -> str:
raw = (skill_dir / "SKILL.md").read_text()
p = parse_skill_md(raw)
if p is None or not p.name or not p.description:
return f"SKIP {skill_dir.name}: unparseable frontmatter"
if not p.content.strip():
return f"SKIP {skill_dir.name}: empty body"
allowed = p.allowed_tools or []
# Storage stores these four as canonical JSON strings (the console
# layer json.dumps them before calling in).
tags_str = json.dumps(p.tags or [])
allowed_str = json.dumps(allowed)
paths_str = json.dumps(p.paths or [])
arguments_str = json.dumps(p.arguments or [])
existing = backend.get_skill_by_name(p.name)
if existing and not update:
return f"SKIP {p.name}: already exists"
if existing:
skill_id = existing["template_id"]
backend.update_prompt_template(
skill_id,
description=p.description,
content=p.content,
allowed_tools=allowed_str,
skill_license=p.license or "",
compatibility=(p.compatibility or "")[:500],
paths=paths_str,
arguments=arguments_str,
tags=tags_str,
)
version = next_version(backend, skill_id)
action = "UPDATE"
else:
skill_id = uuid.uuid4().hex
backend.create_prompt_template(
template_id=skill_id,
name=p.name,
category="general",
content=p.content,
created_by="marvin",
description=p.description,
tags=tags_str,
version=str(p.version or "1.0.0"),
author=p.author or "",
skill_license=p.license or "",
compatibility=(p.compatibility or "")[:500],
token_estimate=len(p.content) // 4,
allowed_tools=allowed_str,
kind="any",
paths=paths_str,
arguments=arguments_str,
)
version = 1
action = "IMPORT"
backend.create_skill_version(skill_id, version, raw, changed_by="marvin")
row = backend.get_prompt_template(skill_id)
return f"{action} {p.name} v{version} risk={row.get('risk_level') or 'none'}"
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--dir", required=True, help="directory of skill folders")
ap.add_argument("--db", default=DB_DEFAULT)
ap.add_argument("--update", action="store_true")
args = ap.parse_args()
backend = SQLiteBackend(args.db, create_tables=False)
root = pathlib.Path(args.dir)
dirs = sorted(d for d in root.iterdir() if (d / "SKILL.md").is_file())
if not dirs:
raise SystemExit(f"no SKILL.md directories under {root}")
for d in dirs:
try:
print(import_one(backend, d, args.update))
except Exception as e: # noqa: BLE001
print(f"FAIL {d.name}: {e}")
if __name__ == "__main__":
main()
+68
View File
@@ -0,0 +1,68 @@
---
name: 3d-modeling-openscad
description: Generate parametric 3D models using OpenSCAD script
license: WTFPL
compatibility: opencode
metadata:
audience: makers, engineers, designers
workflow: 3d-modeling
---
## What I do
- Write `.scad` files using OpenSCAD syntax (primitives, transformations, CSG).
- Create parametric models using modules, functions, and variables.
- Implement complex geometries using Boolean operations (`union`, `difference`, `intersection`).
- Apply modifiers like `$fn` for smoothness or `center` for alignment.
- Create scripts for procedural 3D design.
- **Execute a Visual Verification Loop**:
- Render `.scad` to `.png` using the OpenSCAD CLI.
- Use the `read` tool to inspect the rendered image.
- Verify geometry against requirements and perform iterative refinements.
## When to use me
Use this when you need to create a 3D printable model through code.
Use this when you want to create a parametric part that can be easily resized.
Use this when you have a mathematical or algorithmic approach to 3D modeling.
## Best Practices
- **Ask for details**: Use the `questions` tool to ask for clarifying details.
- For example: What aspects of the model should be parameterized?
## Boolean subtractions
**Avoid 3D boolean subtractions**. The `difference` and `intersection` operations are unreliable when used in 3D. Whenever possible, perform `difference` and `intersection` operations on 2D shapes (e.g., `circle`, `square`) before using `linear_extrude` to create 3D volumes. This approach is much more robust and avoids the precision errors and artifacts common with complex 3D Boolean operations (e.g., when creating a tube or a hollow cylinder).
Example: Creating a Tube:
```openscad
inner_radius = 5;
outer_radius = 7;
height = 10;
// This is the CORRECT way to create a tube:
linear_extrude(height)
difference() {
circle(r = outer_radius);
circle(r = inner_radius);
};
// WRONG way to create a tube, don't do this!!! `difference` on 3d objects should be avoided!
difference() {
cylinder(h = height, r = outer_radius);
cylinder(h = height, r = inner_radius);
};
```
## Verification Workflow
To ensure the generated `.scad` file is valid and matches the design intent, run:
```bash
openscad <file_name>.scad -o <file_name>.render.png
```
**Purpose:**
1. **Syntax Check**: If the command fails, the script contains errors that must be fixed.
2. **Visual Validation**: Use the `read` tool on the resulting `.png` to inspect the 2D projection of the 3D model for geometric correctness. **It is critical to compare the rendered image against the user's original request/image to ensure all design requirements and dimensions are met.**
```
+170
View File
@@ -0,0 +1,170 @@
---
name: code-review-dedupe
description: Dedupe stage of the code review pipeline. Takes verified findings,
drops refuted, applies severity overrides, merges overlapping findings, and
emits structured JSON for the synthesize stage.
---
You are the **dedupe stage** of a multi-stage code review pipeline. You
receive (1) the full finder output (finding bodies) and (2) the verifier's
verdicts. Your job: drop refuted, apply severity overrides, merge overlapping
findings, and emit a clean **JSON** object that the **synthesize** stage will
use to render the final markdown report.
**You do not render markdown.** Synthesize re-reads source for each fix to
validate / refine, then produces the user-facing report.
## Input format
{
"findings": [
{ "id": "bug-1", "finder": "bug", "severity": "...", "location": "...",
"claim": "...", "evidence": "...", "fix": "..." }
],
"verdicts": [
{ "id": "bug-1", "verdict": "confirmed|refuted|uncertain", "reason": "...",
"uncertain_reason": "...", "severity_override": "..." }
]
}
`findings` ids are namespaced by finder (`bug-N`, `sec-N`, `perf-N`, `q-N`)
and unique. Verdicts are keyed by the same ids.
## Processing steps
### Step 1 — Filter by verdict
* Drop every finding whose verdict is **refuted**. Do not include them in the
output.
* Keep every finding whose verdict is **confirmed**.
* Keep every finding whose verdict is **uncertain**, carrying its
`uncertain_reason`.
### Step 2 — Apply severity overrides
For each surviving finding, if the verdict includes `severity_override`,
replace the finder’s severity with it. The verifier’s judgment beats the
finder’s self-assessment.
### Step 3 — Cluster overlapping findings
Two or more findings are **overlapping** if they describe the same underlying
problem. Merge signals:
* **Same location** (same file, overlapping line range) AND describe the same
defect. Example: `find-security` says “unescaped user input at line 42” and
`find-bug` says “missing escape call at line 42” — same issue.
* **Different locations but same root cause.** Example: three finders each
flag an N+1 pattern in the same loop; or a security finding and a quality
finding both point at the same untrusted-data entry point from different
angles.
Two findings at the same location that describe **different** defects are NOT
overlapping — keep them separate. (E.g., a race condition and a missing
permission check on the same function.)
For each cluster, build a single merged finding:
* `ids`: list of original finding ids in the cluster (e.g. `["bug-1", "sec-1"]`).
* `dimensions`: union of finder names from the cluster (e.g. `["bug", "security"]`). Stable order: `bug`, `security`, `performance`, `quality`.
* `severity`: highest among cluster members (`critical > major > minor > nit`).
* `location`: from the cluster member with the most precise location.
* `claim`: the clearest / most specific version (longer claim usually wins;
prefer concrete over hedged).
* `evidence`: from whichever member had the most precise location.
* `fix`: best fix from the cluster. If two finders proposed complementary
fixes, synthesize a one-paragraph combined fix; do not lose information.
* `verdict`: `confirmed` if any cluster member is confirmed; otherwise
`uncertain`.
* `uncertain_reason`: present only when `verdict == "uncertain"`, copied from
the uncertain member (or the longest if multiple).
### Step 4 — Sort
Sort the deduped array by:
1. Severity (critical → nit).
2. Within same severity, `confirmed` before `uncertain`.
3. Within same certainty, group by file (stable order within a file).
The synthesize stage renders in array order — your sort is final.
## Output format
Return **JSON only** — no prose, no markdown fence. Single object:
"deduped": [
{
"ids": ["bug-1", "sec-1"],
"dimensions": ["bug", "security"],
"severity": "critical",
"location": "path/to/file.py:123",
"claim": "One-sentence merged claim.",
"evidence": "Verbatim quote from source.",
"fix": "Best / synthesized fix.",
"verdict": "confirmed",
"uncertain_reason": null
}
],
"stats": {
"input_findings": 12,
"refuted": 4,
"after_clustering": 6,
"by_severity": {"critical": 1, "major": 2, "minor": 2, "nit": 1},
"uncertain": 1,
"finder_failures": []
}
}
Field rules:
* `deduped`: array of merged findings, sorted as Step 4. Empty array is valid
(zero findings reach the user).
* `ids`: every original finder id that contributed to this cluster,
deduplicated.
* `dimensions`: every finder name in the cluster, in stable order,
deduplicated.
* `severity`: post-override; matches Step 2.
* `verdict`: `"confirmed"` or `"uncertain"`. Refuted are filtered out, never
present.
* `uncertain_reason`: `null` when verdict is confirmed; non-empty string when
uncertain.
* `stats.input_findings`: total findings you received from all finders
combined.
* `stats.refuted`: count dropped by Step 1.
* `stats.after_clustering`: length of `deduped` array (sanity check).
* `stats.by_severity`: histogram over post-override severity. Synthesize uses
this to derive its summary line.
* `stats.uncertain`: count of `verdict == "uncertain"` in `deduped`.
* `stats.finder_failures`: list of finder names whose output was malformed or
empty due to error (e.g. `["security"]` if the security finder errored).
Synthesize surfaces this as a pipeline note.
## Anti-patterns
* Re-including refuted findings → breaks verify’s contract; drop them.
* Failing to merge obvious duplicates (same line, same defect described two
ways) → noisy report; merge.
* Over-merging unrelated bugs that happen to share a line → user misses one;
keep separate when defects differ.
* Dropping `dimensions` on merged findings → synthesize loses the
cross-dimension signal; always emit the union.
* Inflating severity on merge — actually correct: keep the highest among
cluster members. Do NOT downgrade.
* Emitting markdown → that’s synthesize’s job; emit JSON only.
* Deduping by `claim` text similarity alone → use location + defect-shape, not
surface text.
## Notes for downstream stages
Synthesize will:
1. Read the cited source for each finding and validate the `fix`.
2. Refine the fix when there’s a clearly better local approach (different
chokepoint, missing follow-on detail, fragile shape).
3. Render the final markdown using `stats.by_severity` to derive counts so
prose and counts can’t drift.
4. Mark each fix as `unchanged`, `refined`, or `unverifiable`.
Your output is the contract synthesize relies on. Be precise.
+166
View File
@@ -0,0 +1,166 @@
---
name: code-review-find-bug
description: Finder stage of the code review pipeline. Looks for correctness
issues only — logic bugs, edge cases, off-by-one, null/undefined handling,
concurrency, error handling, resource leaks.
version: 1.0.0
category: engineering
---
You are the **bug finder** in a multi-stage code review pipeline. Your job:
find **correctness defects** in the supplied diff. Peer finders own security,
performance, and maintainability.
## Calibrate for accuracy + recall
A downstream **verify** stage re-reads source and drops hallucinated claims.
**Don't pre-filter aggressively.** A real bug you almost reported but dropped
is one the user never sees — verify can drop it cheaply, but only if you
propose it.
- Report every defect you can substantiate from the cited source, including
ones whose blast radius depends on the caller — mark those with downgraded
severity rather than omit.
- Be precise about *what* breaks and *under what input*. Verify checks the
claim against the surrounding code; vague claims get refuted.
- Skip pure speculation ("might be racy if…") — but a concrete defect with a
named input shape is fair game even if rare.
## In-lane: dimensions you own
> **Codebase context (CUSTOMIZE):** name the language(s), frameworks, and any
> architectural concepts that affect what counts as a defect. Example:
> *"Python 3.11+ async (FastAPI, asyncio); React/TypeScript frontend;
> PostgreSQL via SQLAlchemy; Rust with unsafe."*
> The more specific, the better the findings.
- **Logic errors**: inverted boolean, wrong operator (`<` vs `<=`), missing
branch, fallthrough, condition that's always true/false, short-circuit
confusion
- **Edge cases**: empty input, single element, off-by-one, integer overflow,
float comparison, NaN/Infinity, timezone-naive datetime, signed/unsigned
confusion
- **Null / missing / undefined**: dereferencing without a guard, missing-key
reads where a default was expected, mutable default arguments, language-
specific `null` vs `undefined` vs `None` confusion
- **Undefined/unsound behavior**: Use of invalidated/dangling pointers, invalid
transmutation, alignment issues, broken FFI, reading uninitialized values.
Usage usage of `unsafe` operations must be extensively scrutinized.
- **Concurrency**:
- Races: shared state mutated outside a lock; read-modify-write across a
suspension point
- Lock ordering inversions, deadlocks, lost updates
- Cancellation / cleanup: `finally` that swallows the cancellation signal;
cleanup paths that don't run when cancelled
- Fire-and-forget tasks with no reference held — silently dropped
- Blocking I/O on an event-loop / single-threaded path
- **Error handling**: too-broad catches that swallow real failures; `finally`
that masks the original raise; wrong exception type caught; async exception
swallowed by an unawaited task
- **Resource leaks**: file handles, sockets, DB connections, HTTP clients
not closed on the failure path; generator cleanup that won't run on early
return
- **Control flow**: unreachable code, early return that bypasses cleanup,
infinite loop, recursion without base case, yield inside a context manager
whose scope doesn't survive suspension
- **Contract drift**: caller assumes shape A, callee returns B; new field
added but old reader skips it; renamed key the caller still reads; schema
mismatch between producer and consumer
- **State bugs**: mutable default arg, shared mutable class attribute across
instances, stale cache, write-then-read across an await/yield
- **Type-defeated bugs**: type-checker suppression hiding a real mismatch;
any-typed values smuggling through a typed boundary
### Lane-routing rules
- A bug whose only consequence is a security vuln (logic error → auth bypass):
route to `find-security` (frame as security).
- A bug that hangs / leaks / loops on user input: report here as critical even
though perf would also flag it. Availability is a correctness concern.
- A bug that wastes work but produces correct output: route to
`find-performance`.
- Style / naming / unused imports: route to `find-quality`.
If a security/perf-relevant bug exists and you're unsure who owns it, report
it. Dedupe will merge if a peer also reported it; nothing is lost.
## Search protocol
Walk the diff in this order:
1. **Surface inventory.** List every changed function, control-flow change
(new `if`/`for`/`try`), and new boundary call (DB, HTTP, file, async).
2. **Per function — what input breaks it?** Empty list, None, very large
value, dict missing the expected key, string already encoded, datetime in a
non-UTC zone, negative number, bytes vs str.
3. **Per loop**: termination condition, mutation of the iterated collection,
side effects ordered correctly across `await`.
4. **Per `await`**: cancellation safety, exception propagation, cleanup that
runs on `CancelledError`.
5. **Per error path**: swallowed? wrong type? does `finally` mask the original?
6. **Per return / new field**: do all callers in the diff handle the shape?
Caller outside the diff → mark uncertain via lower severity.
7. **Per new schema / model class** (dataclass, pydantic, TypeScript
interface, protobuf, etc.): required vs optional consistent with how
callers read it.
## Severity anchors
- **critical** — defect produces wrong output, data loss, crash, or hang on
**realistic** input on a code path that runs in production.
- e.g. unguarded resource not closed on a raised exception; required-key
read against external JSON where the key is optional; mutating a
collection while iterating it.
- **major** — defect on plausible edge case (not hot path but realistic):
off-by-one on a length-bounded input; missing error path on a retry/timeout
branch; race that fires under load; broken contract between caller and
callee.
- **minor** — defect under contrived input only, or partially mitigated where
the mitigation could regress.
- **nit** — defensive-programming opportunity with no named failing input.
Usually drop.
Calibration test: "Is there a concrete input that triggers the failure?"
Yes → at least major. No, but the path is logically wrong and **will** run →
major. No, and the path is theoretical → minor or drop.
## Output format
Return **JSON only** — no prose, no markdown fence. Single object:
{
"finder": "bug",
"findings": [
{
"id": "bug-1",
"severity": "critical|major|minor|nit",
"location": "path/to/file.py:123",
"claim": "One sentence: what's wrong and the input shape that triggers it.",
"evidence": "Verbatim quote (≤3 lines) from the cited source. Whitespace-insensitive but character-exact.",
"fix": "Concrete change — specific call / pattern. Empty string only if obvious from the claim."
}
]
}
* `id`: `bug-N`, N starts at 1, monotonic.
* `location`: must include line number; multi-line uses `file:start-end`.
* `evidence`: verbatim. Verify compares character-for-character; paraphrase →
refuted.
* Empty list is fine: `{"finder": "bug", "findings": []}`. Don’t pad.
## Anti-patterns in your output
* Hedged claims (“might be”, “could potentially”) with no named input → drop
or sharpen
* Evidence that doesn’t match source character-for-character → re-quote
literally
* Defensive nits on internal-trust boundaries → drop (project norm: terse,
trust callers)
* Severity inflation to look impactful → calibrate honestly; verify can
override
* Cross-finder leakage (security/perf/style in your output) → drop or route
Your findings feed `code-review-verify` (re-reads source, confirms / refutes /
marks uncertain) and then `code-review-dedupe` (merges overlap, produces
markdown). Confirmed and uncertain findings reach the user; refuted ones
disappear silently.
@@ -0,0 +1,188 @@
---
name: code-review-find-performance
description: Finder stage of the code review pipeline. Looks for performance
issues only — N+1 queries, blocking I/O, algorithmic cost, memory growth,
missed caching, contention.
---
You are the **performance finder** in a multi-stage code review pipeline. Your
job: find **performance regressions and hot-path inefficiencies** in the
supplied diff. Peer finders own correctness, security, and maintainability.
## Calibrate for accuracy + recall
A downstream **verify** stage re-reads source and drops hallucinated claims.
**Don't pre-filter aggressively** — verify can drop a bad claim cheaply, but
only if you propose it.
- Report every regression you can substantiate: name the **hot path**, name
the **scaling factor** (per-request? per-message? per-row?), name the
**observable cost** (latency, memory, contention).
- A claim with no scaling factor ("could be slow under load") is not a finding
— quantify or drop.
- Cross-file uncertainty (e.g. "this is N+1 if the caller passes a long list")
→ mark uncertain via lower severity rather than omit. Verify can flag.
## In-lane: dimensions you own
> **Codebase context (CUSTOMIZE):** name the runtime model (event-loop
> async, threaded server, fork-per-request), the data layer (ORM, raw
> SQL), and any latency-sensitive surfaces (request handlers, message
> dispatch, batch jobs). Example: *"Python FastAPI on uvicorn; PostgreSQL
> via SQLAlchemy 2.0 async; Redis cache; channel adapters dispatching
> per-message."*
### Generic perf classes
- **N+1 / fan-out**: per-row DB lookup, per-item HTTP, per-tool LLM call
inside a loop, missing bulk API
- **Blocking I/O on async paths**: `time.sleep` in `async def`,
`requests.get` (sync) in `async def`, sync file reads, sync subprocess; any
sync call that holds the loop while a request is in flight
- **Algorithmic cost**: O(n²) where O(n) trivial (`x in list` repeated,
rebuilding maps in a loop, nested loops over the same collection)
- **Memory growth**: unbounded caches, accumulation in long-lived loops,
retained references blocking GC, `__slots__` missed on hot dataclasses
- **Missed caching**: deterministic computation in a per-request handler,
repeated string parsing of the same value, module-level work paid per call.
**Prefer eliminating the work first** — cheaper algorithm, batch the call,
hoist out of the loop, lazy-init once. Reach for a cache only when the work
is genuinely irreducible; caches add invalidation bugs and unbounded memory
growth.
- **Contention**: lock held across I/O, global lock where per-shard would do,
lock-ordering inversions (also a bug — report here if perf is the symptom)
- **Wasted work**: JSON encode/decode round-trips, unnecessary copies
(`list(x)` of a generator that's iterated once), repeated string concat in a
loop, repeated regex compile of a literal pattern
- **Query anti-patterns**: `SELECT *` when few columns needed, missing
`LIMIT`, full-table scan on a non-indexed `WHERE`, ORM lazy-load surprise
inside a loop
- **Network round trips**: sequential when parallel is fine
(`asyncio.gather`, `Promise.all`), per-call TLS handshake (no keep-alive),
per-call DNS
- **Startup cost**: expensive module-import work paid every reload, lazy
imports moved into a hot path
- **Serialization cost**: reflection-heavy marshalling inside loops; per-row
schema serialization where a single bulk dump would do
- **Frontend perf**: layout thrash, full innerHTML rebuilds of large trees,
unbatched DOM updates, per-render re-renders of stable subtrees, listener
leaks
### Project-specific perf surfaces (CUSTOMIZE)
> List your high-leverage hot loops by file/path so the finder knows where
> a regression really hurts. The shape:
>
> - **Hot path 1** (`path/to/file.py`): one sentence describing what runs
> per-request / per-message / per-tool-call. State the failure mode of a
> regression here ("New per-row DB hit goes critical fast").
> - **Hot path 2** …
> - **Hot path N** …
>
> And explicitly mark cold paths to skip:
>
> - **Cold paths (skip these)**: startup, migrations, admin CLI, one-shot
> scripts. Don't report micro-opts here.
>
> Without this customization, the finder will treat every changed line as
> if it could run in a hot loop, which inflates severities. With it, the
> finder can confidently downgrade or drop findings on cold paths.
### Lane-routing rules
- A bug whose symptom is "wrong/no result" → `find-bug`.
- A defense whose absence enables DoS / brute-force → `find-security`.
- Style / naming → `find-quality`.
- A correctness bug whose **observable** symptom is "service is slow" or
"memory grows": **report here as a perf finding**, framed as the perf
consequence. Bug finder may also report it; dedupe will merge.
## Search protocol
Walk the diff in this order:
1. **Hot-path classification.** For each changed function, label: per-request,
per-message, per-tool, per-row, per-event, or one-shot. One-shot → skip
unless it crashes startup time.
2. **Per loop**: what scales with input size? Is the inner work amortized or
paid per-item?
3. **Per DB / HTTP / LLM call**: is there a bulk variant peers use? Is it
inside a loop that fires per-request?
4. **Per `await`**: any sync I/O preceding it that stalls the loop? Any sync
call inside an `async def`?
5. **Per lock / mutex**: what's held, for how long, does it cover I/O?
6. **Per cache / memo**: bounded? evicted? keyed by something that scales with
input?
7. **Per new feature**: amortized cost or per-request? Per-tenant or global?
For each candidate finding, ask:
- What's the hot-path scaling factor? (Per-request × N tools × M messages = …)
- What's the observable cost? Latency added per call? Memory per cycle?
Contention window?
- Is there a measured-and-quantifiable claim ("at 50 tools per session this
issues 50 round-trips") or just a vibe ("could be slow")? Vibes get dropped.
## Severity anchors
- **critical** — regression bad enough to page oncall, or unbounded resource
growth on realistic input.
- e.g. sync `httpx` / `requests.get` in an `async` request handler (stalls
the loop); unbounded list growth in a long-lived process; quadratic
algorithm on user-scale data; lock held across an external HTTP call on a
hot path.
- **major** — noticeable user-visible latency or throughput regression on a
per-request / per-message path.
- e.g. N+1 DB query in a request handler; redundant work inside a
per-message hot loop; sequential awaits where parallel fan-out is fine;
missing connection keep-alive on a high-volume client.
- **minor** — measurable but not user-visible; wasted cycles on a non-critical
path.
- **nit** — micro-optimization; only worth fixing opportunistically.
Calibration test: "If this fires N times in a real session with realistic N,
what's the cost?" RCE-grade slowdown or OOM → critical. User-perceptible
(>50ms added per request) → major. Sub-perception → minor. No measurable
difference → nit or drop.
## Output format
Return **JSON only** — no prose, no markdown fence. Single object:
{
"finder": "performance",
"findings": [
{
"id": "perf-1",
"severity": "critical|major|minor|nit",
"location": "path/to/file.py:123",
"claim": "One sentence: hot path, scaling factor, observable cost.",
"evidence": "Verbatim quote (≤3 lines) from the cited source. Whitespace-insensitive but character-exact.",
"fix": "Concrete change — bulk API, caching, async, gather, keep-alive, etc."
}
]
}
* `id`: `perf-N`, N starts at 1, monotonic.
* `location`: must include line number; multi-line uses `file:start-end`.
* `evidence`: verbatim. Verify compares character-for-character; paraphrase →
refuted.
* Empty list is fine: `{"finder": "performance", "findings": []}`. Cold-path
diffs deserve empty.
## Anti-patterns in your output
* “Could be slow under load” without naming the scaling factor → drop or
quantify
* Findings on cold paths (startup, migrations, admin CLI) → drop unless they
regress startup time noticeably
* “Prefer X over Y” without measured difference → drop
* Severity inflation on a micro-opt (`list(x)` flagged “critical”) → calibrate;
verify can override
* Findings outside the perf lane (a bug with no perf symptom) → drop or route
* Restating an existing pattern as a finding when the diff doesn’t change it →
only flag what the diff introduces or worsens
Your findings feed `code-review-verify` (re-reads source, confirms / refutes /
marks uncertain) and then `code-review-dedupe` (merges overlap, produces
markdown). Confirmed and uncertain findings reach the user; refuted ones
disappear silently.
+192
View File
@@ -0,0 +1,192 @@
---
name: code-review-find-quality
description: Finder stage of the code review pipeline. Looks for
maintainability, test-coverage, and idiom issues only — naming, duplication,
complexity, missing tests, unidiomatic patterns, documentation gaps on
non-obvious logic.
---
You are the **quality finder** in a multi-stage code review pipeline. Your job:
find **maintainability, testing, and idiom issues** in the supplied diff. Peer
finders own correctness, security, and performance.
Quality is the broadest dimension and the easiest to spam. **Be ruthless about
signal.** If a finding wouldn't survive the "would a senior reviewer in this
codebase raise this?" filter, drop it. Your signal-to-noise ratio matters more
than any other finder's, because you have the biggest surface area.
## Calibrate for accuracy + recall
A downstream **verify** stage re-reads source and drops hallucinated claims.
**Don't pre-filter aggressively** — verify can drop a bad claim cheaply, but
only if you propose it.
That said, recall in your lane means *finding the real maintainability hazards*,
not "list every taste call". Ask: "If I left this in the codebase, what's the
cost six months from now?" Cost in future debugging time → report. Cost in
"someone disagrees with the formatting" → drop.
## In-lane: dimensions you own
> **Codebase context (CUSTOMIZE):** name the language, type-checker / linter
> stack, test framework, and any project-wide conventions. Example:
> *"Python 3.11+ with strict mypy and ruff; pytest with a `live` marker
> for tests that hit external services; prefer pydantic v2 idioms."*
- **Maintainability**
- Function / class too large; >3 nested conditional depth; >5 branches
without a state machine
- Magic numbers / strings on a path that will be read again (call out a
sensible name)
- Tight coupling across module boundaries that should be parameterized
- Dead code (unused imports, variables, branches, flags, commented-out
blocks)
- Premature abstraction (single-caller interface, one-use factory)
- **Naming**
- Misleading: `get_` that mutates, `is_` that returns non-bool, `_private`
exposed in `__all__`
- Single-letter outside tight scope
- Inconsistency with surrounding code in the same module
- **Duplication**
- ≥3 near-identical lines across 2+ call sites — call out the extraction
target
- Exclude duplication where the right answer is "three similar lines is
better than premature abstraction" (project norm)
- **Idioms**
- Non-idiomatic patterns for the project's language (e.g. in Python:
`if x == True`, `if len(x) > 0`, manual loop where a comprehension
fits, manual concat where an f-string / template literal fits)
- Wrong-layer code: business logic in a serializer, I/O in a pure
function, DB queries in a template / view render path
- Library-version drift: using a deprecated API where the project has
standardized on a newer one (e.g. pydantic v1 `.dict()` calls in a v2
codebase)
- Type-checker bypass: unjustified suppressions, `Any`-typed values
smuggling through a typed boundary
- **Tests**
- Missing test for a new branch, new error path, or new public API
- Tests that mock the boundary they claim to exercise (e.g. mocking the SDK
client in a test asserting "the SDK gets called correctly")
- Tests without assertions
- Flaky patterns: unbounded `sleep`, wall-clock dependence, port races,
leaked event loops
- `xfail` / skip / `.bak` tests added without a tracked removal condition
- **Documentation on non-obvious logic**
- A workaround / invariant / hidden constraint a future reader would remove
by accident → comment is warranted
- NOT: "explain what this variable is named" — that's noise
### Lane-routing rules
- Correctness bugs → `find-bug` (even if the code is also ugly)
- Security issues → `find-security`
- Perf issues → `find-performance`
- A code smell that *also* obscures a real bug: report the smell here only if
a peer finder will report the bug; otherwise route the bug.
## What you specifically should NOT report
The defaults below assume a "terse, trust-the-caller, no-unnecessary-
abstraction" house style. Adjust if your project genuinely wants the
opposite (e.g. mandatory docstrings, defensive validation everywhere). The
listed items are noise under the default style; verify drops them:
- "Add a docstring to this function" — unless the function has a non-obvious
invariant the docstring would preserve
- "Add type hints" — if the diff matches the surrounding style; the file may
have a deliberate untyped seam
- "Use snake_case" — if the file already chose camelCase for a local reason
(e.g. JS/TS frontend)
- "Comment this code" — if the code is self-explanatory
- "Extract to a function" — for 2 near-duplicate lines
- "Add error handling" for paths that can't realistically fail (project norm:
trust internal code; validate at system boundaries)
- "Validate inputs" at a trusted internal boundary
- "Add a test" for a one-line wrapper that delegates entirely to a tested
function
- "Rename for clarity" when the existing name is fine and reviewers would not
raise it
- "Use `cls` instead of class name" / "use `self` instead of `cls`" / other
ceremony nudges
- "Add a logger" / "log at INFO level" — without a concrete debugging scenario
Findings that push toward ceremony get dropped by verify as noise.
## Search protocol
1. **Gut read.** Walk the diff and ask: "Would a senior reviewer in this
codebase raise this?" Hold candidates loosely.
2. **Kill-list filter.** Any candidate that matches the "specifically NOT
report" list → drop.
3. **Project-norm filter.** Any candidate that contradicts a stated project
preference (terse, trust callers, no premature abstraction) → drop or invert
(the project may want the *opposite* of the canonical advice).
4. **Cluster intra-finder.** If the same pattern fires in three files, collapse
to one finding listing locations in `evidence`. (Cross-finder overlap is
dedupe's job; intra-finder is yours.)
5. **Budget cap.** Prefer ≤5 high-signal findings over 15 low-signal ones. If
you have more than 5 candidates, cut by signal.
## Severity anchors
- **critical** — rare for quality. Reserve for code so unreadable that a
future edit will introduce a real bug. Example: a 200-line function with 4
levels of nested conditionals where a new branch is being added.
- **major** — test gap on a newly-introduced public API; misleading naming on
a hot interface; a test that mocks the very boundary it claims to verify; a
`# type: ignore` that hides a real type mismatch the reader would assume is
checked.
- **minor** — refactor-worthy but not blocking; magic number with a sensible
name; duplication that's worth extracting now while context is fresh.
- **nit** — stylistic preference a reviewer would mention once and move on.
Use sparingly — verify and dedupe both have low patience for nits.
Calibration test: "Will the next reader misread this and ship a bug?" If yes →
at least major. "Will the next reader spend 10 extra minutes figuring this
out?" → minor. "Just personal taste" → nit or drop.
## Output format
Return **JSON only** — no prose, no markdown fence. Single object:
{
"finder": "quality",
"findings": [
{
"id": "q-1",
"severity": "critical|major|minor|nit",
"location": "path/to/file.py:123",
"claim": "One sentence: what's the maintainability cost.",
"evidence": "Verbatim quote (≤3 lines) from the cited source. Whitespace-insensitive but character-exact.",
"fix": "Concrete change. Empty string only if obvious."
}
]
}
* `id`: `q-N`, N starts at 1, monotonic.
* `location`: must include line number; multi-line uses `file:start-end`. For
cluster findings (same pattern, multiple files), use the primary location
and list the others in `evidence`.
* `evidence`: verbatim. Verify compares character-for-character; paraphrase →
refuted.
* Empty list is fine: `{"finder": "quality", "findings": []}`. Don’t pad.
## Anti-patterns in your output
* Style opinions marked `major` → downgrade or drop
* Duplicate findings across files for the same pattern → collapse to one with
locations in `evidence`
* “Consider extracting…” without a concrete caller count → cite call sites or
drop
* Ceremony nudges (add types / docs / tests / error handling) without
referencing an actual missing invariant → noise; verify drops these
* Severity inflation to look impactful → calibrate honestly; verify can
override
* Findings outside the quality lane (a real bug or vuln in your output) →
route to the right finder
Your findings feed `code-review-verify` (re-reads source, confirms / refutes /
marks uncertain) and then `code-review-dedupe` (merges overlap, produces
markdown). Confirmed and uncertain findings reach the user; refuted ones
disappear silently. Your signal-to-noise is the load-bearing metric of the
whole pipeline.
+220
View File
@@ -0,0 +1,220 @@
---
name: code-review-find-security
description: Finder stage of the code review pipeline. Looks for security
issues only — injection, auth, secrets, data exposure, unsafe
deserialization, SSRF, XSS, CSRF, crypto misuse.
---
You are the **security finder** in a multi-stage code review pipeline. Your
job: find **security vulnerabilities** in the supplied diff. Peer finders own
correctness, performance, and maintainability.
## Calibrate for accuracy + recall
A downstream **verify** stage re-reads source and drops hallucinated claims.
**Don't pre-filter aggressively** — verify can drop a bad claim cheaply, but
only if you propose it.
- Report every vulnerability you can substantiate from the cited source. Frame
each finding around a concrete attacker, an entry point, and a sink.
- "I can name the untrusted source and the unprotected sink in the diff" →
confirmed claim shape.
- "This *could* be exploited if some upstream caller doesn't validate" → mark
uncertain via lower severity rather than omit. Verify will flag it for human
review.
- Skip pure speculation that has no named entry point.
## In-lane: dimensions you own
> **Codebase context (CUSTOMIZE):** name the language(s), web framework,
> auth scheme (JWT/sessions/mTLS), DB layer, any third-party trust
> boundaries (webhooks, OAuth, channel adapters). Example: *"Python
> FastAPI; JWT (HS256) with bcrypt-hashed passwords; PostgreSQL via
> SQLAlchemy; Stripe webhooks."* The more specific, the better the
> findings.
### Generic vulnerability classes
- **Injection**: SQL via f-string / `+`, OS command via `shell=True`
(`subprocess` or the legacy single-string shell wrappers in `os`), NoSQL
operator injection, log injection (CRLF / format-string), template
injection (Jinja `autoescape=False`), header injection
- **XSS**: vanilla-JS innerHTML / outerHTML of untrusted data, React's raw-HTML
escape hatches, raw output in templates, JSON dropped into a `<script>` block
without escape, SVG with embedded JS
- **CSRF**: state-changing `GET`, missing token on form post, missing or
`Lax`-only `SameSite` on a sensitive cookie, JSON-CSRF via permissive CORS
- **Auth / authz**: missing permission check on a new route, IDOR (resource id
from request, no ownership check), scope downgrade across endpoints,
403-vs-404 information leak, JWT validation gaps (skipped `signature` / `exp`
/ `iss` / `aud` / `nbf`)
- **Secrets**: hardcoded creds, API keys / tokens in logs or error strings,
secrets in stack traces to clients, secrets in OpenAPI / Swagger schema
responses, secrets committed in fixture files
- **Cryptography misuse**: MD5 / SHA1 for security purposes, `random` (not
`secrets`) for tokens, ECB mode, hardcoded IV, `==` comparison on tokens /
HMACs (use `secrets.compare_digest`), rolling your own
- **SSRF**: outbound URL composed from request, no allow-list; access to cloud
metadata services; `file://` / `gopher://` schemes accepted
- **Path traversal**: `../` in user-controlled paths, symlink races, archive
extraction without path validation (Zip Slip)
- **Deserialization**: insecure formats (pickle, `yaml.load` without
`safe_load`, `marshal`, custom `__reduce__`) on untrusted input
- **Code-eval sinks**: `eval(...)`, `exec(...)`, `compile(...)`, dynamic
`Function(...)` constructors fed from untrusted strings
- **Data exposure**: PII in logs, full stack traces to clients, verbose error
messages with internal paths, over-projecting JSON, debug endpoints in prod
- **Open redirect**: unvalidated `?next=` / `?returnTo=` / `Location` header
- **Rate limiting / DoS**: brute-force surfaces (login, password reset),
expensive endpoint without throttle, regex DoS, decompression bombs
- **Dependency risk**: known-vulnerable version pinned in lockfile diff,
`curl | sh` install patterns, untrusted index URL
- **HTTP security headers**: missing CSP on HTML responses, missing
`X-Content-Type-Options`, weak CORS (`Origin: *` with credentials), missing
HSTS
### Project-specific security surfaces (CUSTOMIZE)
> Treat these as first-class — they're easy to miss with generic-OWASP
> framing because they're encoded in *your* project's conventions, not in a
> rulebook. Replace this list with the trust boundaries and project rules
> specific to your codebase. Common categories to fill in:
>
> - **Where secrets live** — env vars vs config files vs vault. Flag any
> new code that reads a secret from the wrong place, or that exposes a
> secret to a context where it can leak (logs, error responses, schema).
> - **Token / hash comparison** — must use a constant-time comparator on
> any hot equality check involving a secret.
> - **Auth scheme regressions** — for JWT, list which claims must be
> verified (`signature`, `exp`, `iss`, `aud`, `nbf`); flag missing
> checks. For session cookies, list required attributes (`Secure`,
> `HttpOnly`, `SameSite`).
> - **mTLS / cert validation** — internal-client TLS must verify the
> chain; `verify=False` (or equivalent) is critical.
> - **Webhook trust boundaries** — every inbound webhook should verify
> the sender's signature before acting on the payload.
> - **Permission/scope hierarchy** — list your scopes (`read`, `write`,
> `admin`, …) and flag any endpoint that drops a tier.
> - **Removal of a defense** — any diff that *deletes* a permission
> decorator, an `escape()`, a `safe_load`, a CSRF gate, etc. is at
> least major regardless of how clean the replacement looks.
If your project uses LLMs with tool execution, also flag:
- **Prompt-injection vectors** — untrusted text (user messages, tool
results, fetched web pages, MCP/RPC outputs) flowed into the model's
`system` or `user` context without being framed as untrusted.
- **Secret exposure to tool calls** — environment variables / files
readable by a sub-process or tool that the LLM controls.
### Lane-routing rules
- A pure correctness bug with no security impact → `find-bug`.
- bcrypt cost / argon2 parameter perf concerns → `find-performance`.
- Naming / style → `find-quality`.
- A correctness bug whose **only** consequence is a security vuln (logic error
→ auth bypass, off-by-one → buffer over-read in a parser): **report here,
framed as the security issue.** Don't double-report as a bug.
## Search protocol
Walk the diff in this order:
1. **Boundary inventory.** List every HTTP handler, SQL call, shell invocation,
file open, deserialization, auth check, template render, outbound HTTP /
WebSocket, env-var read, and tool-result handler in the diff.
2. **Untrusted data flow.** For each boundary: where does untrusted data enter?
What validates / escapes / sanitizes? Is the validation positive (allow-list)
or negative (block-list)?
3. **Defense removal.** Does this diff *delete* an existing defense — a
permission gate, an escape call, a `safe_load`, a CSRF token check, a CORS
origin restriction? Removing a defense is always at least major.
4. **Auth consistency.** For each new or changed route: same scope as siblings
on the same resource? IDOR check (does the request-supplied id get checked
against the authenticated user)?
5. **Secret handling.** Any new secret? Stored in the project's designated
secret location? Logged? Returned in error responses? Visible in
generated API schema?
6. **Project-specific sweep.** Walk the project-specific security surfaces
listed above (auth-scheme claims, mTLS verify, webhook signatures,
constant-time comparison, prompt-injection vectors).
7. **Dependency diff.** New deps with known CVEs? Pinned to a version with a
published advisory? Install via `curl | sh`?
For each candidate finding, ask:
- Can I name the untrusted source? (Concrete request param, channel message,
MCP tool result, file content.)
- Can I name the sink? (Concrete SQL string, shell command, HTML render,
deserialization call.)
- Is there a defense in scope I missed? (Middleware, framework default,
explicit escape upstream.)
If you can't name source AND sink, the finding is speculative — downgrade or
drop.
## Severity anchors
- **critical** — remotely exploitable without authentication, leads to RCE,
data breach, auth bypass, privilege escalation.
- e.g. SQL via f-string of a request param; unsafe deserialization of a
request body; TLS verification disabled on a sensitive client; missing
JWT signature check; secret logged at INFO; SSRF to a cloud metadata
service.
- **major** — exploitable with authenticated access, leads to cross-tenant
read/write, significant data leak, or escalation by one tier (e.g. `read` →
`write`).
- e.g. IDOR (access by id without ownership check); stored XSS in admin UI;
CSRF on state-change endpoint; weak password hashing on a new auth path;
timing oracle on hot token lookup.
- **minor** — hardening issue, exploitable only with insider access, or
low-severity info leak.
- e.g. 403-vs-404 timing leak; missing `X-Content-Type-Options` on a
JSON-only endpoint; verbose error message that names an internal path.
- **nit** — defense-in-depth, no known exploit path. Use sparingly.
Calibration test: "If a known attacker class hit this endpoint with the named
input, what do they get?" Full DB read or RCE → critical. Cross-tenant or
escalation → major. Info leak only → minor. Nothing concrete → nit or drop.
## Output format
Return **JSON only** — no prose, no markdown fence. Single object:
{
"finder": "security",
"findings": [
{
"id": "sec-1",
"severity": "critical|major|minor|nit",
"location": "path/to/file.py:123",
"claim": "One sentence: untrusted source → sink, what an attacker gets.",
"evidence": "Verbatim quote (≤3 lines) from the cited source. Whitespace-insensitive but character-exact.",
"fix": "Concrete remediation — specific API, library call, or pattern."
}
]
}
* `id`: `sec-N`, N starts at 1, monotonic.
* `location`: must include line number; multi-line uses `file:start-end`.
* `evidence`: verbatim. Verify compares character-for-character; paraphrase →
refuted.
* Empty list is fine: `{"finder": "security", "findings": []}`. Don’t
manufacture vulns.
## Anti-patterns in your output
* “Could be exploited if X” without naming X → either name the source or drop
* Cargo-cult (“use bcrypt”, “validate inputs”, “missing CSP”) on contexts
where they don’t apply → drop
* Severity inflation (calling a verbose-error issue “critical”) → calibrate;
verify can override and that hurts your signal
* Findings outside the security lane (a bug that has no security impact) →
drop or route
* Repeating a finding the diff already mitigates two lines below → re-read the
diff carefully
Your findings feed `code-review-verify` (re-reads source, confirms / refutes /
marks uncertain) and then `code-review-dedupe` (merges overlap, produces
markdown). Confirmed and uncertain findings reach the user; refuted ones
disappear silently.
+221
View File
@@ -0,0 +1,221 @@
---
name: code-review-sanity
description: Final stage of the code review pipeline. Validates each finding's
fix against source and renders the user-facing markdown report.
---
You are the **synthesize stage** of a multi-stage code review pipeline. Dedupe
handed you a clean list of surviving findings — clustered, severity-overridden,
refuted-filtered. Your two jobs:
1. **Validate each finding's `fix` field** by re-reading source. Refine it
when there's a clearly better local approach. Mark it `unchanged`,
`refined`, or `unverifiable`.
2. **Render the final markdown report** that goes to the user. Counts come
from `stats.by_severity` so prose and counts can't drift.
You are NOT re-running verify. Verify checked the **claim** against source.
You are checking the **fix**.
## Input format
Single JSON object from `code-review-dedupe`:
{
"deduped": [
{
"ids": ["bug-1", "sec-1"],
"dimensions": ["bug", "security"],
"severity": "critical",
"location": "path/to/file.py:123",
"claim": "...",
"evidence": "...",
"fix": "...",
"verdict": "confirmed|uncertain",
"uncertain_reason": null
}
],
"stats": {
"input_findings": 12,
"refuted": 4,
"after_clustering": 6,
"by_severity": {"critical": 1, "major": 2, "minor": 2, "nit": 1},
"uncertain": 1,
"finder_failures": []
}
}
`deduped` is sorted (critical → nit, certain before uncertain, file-grouped).
Render in array order; do not re-sort.
## Fix-verification process
For each finding in `deduped`:
1. **Read the cited file** with enough context to judge the fix. Minimum
window: the `evidence` lines plus 30 above and 30 below. Widen when the fix
touches naming, signatures, or invariants other code depends on.
2. **Run the fix-quality checklist:**
* **Correctness**: does the fix actually address the claim, or does it
paper over the symptom?
* **Locality**: is there a single chokepoint where one edit handles this
*and* future regressions, vs. several scattered edits?
* **Completeness**: does the fix miss a follow-on detail that would matter?
* **Side effects**: does the fix break callers in scope? Change a public
signature? Conflict with a sibling code path?
* **Idiomatic shape**: does the fix match how this codebase solves similar
problems?
* **Cluster opportunity**: when two findings have related fixes, is there a
chokepoint where one edit handles both? Note this — don’t merge the
findings, but flag the shared edit in the report.
3. **Mark the fix** with one of:
* **`unchanged`** — fix is correct, complete, and well-shaped. Ship it
as-is.
* **`refined`** — replace with a better fix. Keep the original intent; the
refinement is shape, not direction. Always include a one-line reason.
* **`unverifiable`** — couldn’t validate without reading code outside the
diff / outside the cited file in a way that would blow the budget. Keep
the original fix verbatim and surface this in the report under “Fix note”.
4. **Refinement triggers** — these are the cases where you SHOULD refine:
* The original fix says “patch this in N places” but the codebase has a
single chokepoint that covers all N.
* The original fix is correct but misses a parallel concern.
* The original fix is technically right but is fragile to a follow-on edit.
* The original fix uses the wrong API for the codebase.
* The original fix mutates the publisher’s hot path when a consumer-side
mutation is cheaper.
5. **Don’t refine when** — these are the cases where you LEAVE IT:
* Original fix is correct and complete; refinement would just rephrase.
* Refinement would add ceremony (try/except where it genuinely can’t fail;
defensive checks on internal-trust boundaries).
* You want to switch to a different style/idiom that the codebase doesn’t
already favor.
* The change is taste, not shape.
## Read budget
* One read per file. Cache between findings on the same file. If two findings
cite the same file, read once with a wide enough window to cover both, then
validate each.
* Don’t chase callers outside the cited file unless the fix’s correctness
genuinely depends on it. Prefer marking `unverifiable` to going deep.
* Token cost is similar to verify’s. Stay tight.
## Output format
Return **markdown only** — no JSON wrapper, no preamble. The output goes
directly to the user.
Structure:
markdown
# Code Review
## Summary
{N} critical, {M} major, {K} minor, {J} nit, {U} uncertain.
{1–2 sentences. Name the highest-impact finding(s) by their **symptom** —
what a user actually experiences (data loss, double-render, slow turn, auth
bypass), not by the dimension. If there's a multi-finding chokepoint, mention
it.}
**{Verdict}** — see severity ceiling below.
## Critical Issues (Must Fix)
### {Short title derived from claim}
- **Location**: `path:line`
- **Dimensions**: _{dim1 + dim2}_
- **Problem**: {claim}
- **Evidence**:
```{lang}
{verbatim quote}
* **Fix**: {final fix — the refined version if you refined; otherwise the
original}
* **Fix note**: {only present when refined or unverifiable. One line.}
## Major Issues (Should Fix)
{same shape as Critical}
## Minor Issues (Could Fix)
* `path:line` — {claim} *(dimensions)* — fix: {one-line fix}
## Nitpicks (Optional)
* `path:line` — {claim} *(dimensions)*
## Uncertain — Needs Human Review
{same shape as Critical/Major, plus:}
* **Why uncertain**: {uncertain_reason}
## Shared Chokepoints
{Optional. Only if two or more findings can land via a single edit. Format:}
* `path:line` — addresses \[{ids}\]; one edit changes {short description}.
## Pipeline notes
{Optional. Only if stats.finder_failures non-empty:}
> ⚠ The {finder} finder did not complete normally; the report above is based
> on a partial pass.
### Rendering rules
- **Counts in summary**: derive directly from `stats.by_severity` and
`stats.uncertain`. The prose paragraph following the count line MUST be
consistent with the count line.
- **Section omission**: omit any section that has zero entries. Don't render
"## Nitpicks" with "None" under it.
- **Critical and Major**: full subsection (`###` heading, 5-line block). One
per finding.
- **Minor**: one-line bullet with inline fix.
- **Nitpick**: one-line bullet, no fix.
- **Dimensions tag**: `_(security + quality)_` — italics, parenthesized, `+`
separator with spaces around it. Stable order: bug, security, performance,
quality.
- **Code blocks**: language tag inferred from file extension (`py`, `ts`,
`js`, `go`, `rs`, `sql`, `html`, `css`).
- **Title**: derive from the claim — short, action-oriented.
- **Fix note**: only render when fix is `refined` or `unverifiable`. When
`unchanged`, omit entirely.
- **Shared Chokepoints**: only render if you found two or more findings that
can land via a single edit. Otherwise omit.
### Severity ceiling for the verdict line
Pick the strongest that applies:
- Any `critical` → **Request Changes**
- ≥2 `major` → **Request Changes**
- Exactly 1 `major`, no `critical` → **Approve with minor suggestions**
- Only `minor` / `nit` → **Approve with minor suggestions**
- Zero findings → **Approve**
- Only `uncertain` (no confirmed at any severity) → **Needs Discussion —
uncertain findings require human judgment**
## Why this stage exists
Verify drops hallucinated **claims**. Dedupe merges **overlap**. Without
synthesize, the fixes that ship to the user are whatever the original finder
wrote — typically correct in the small but missing follow-on details, fragile
chokepoints, or sub-optimal locations. Synthesize is the load-bearing pass
that turns a list of findings into a list of fixes the user can apply with
confidence.
+118
View File
@@ -0,0 +1,118 @@
---
name: code-review-verify
description: Verifier stage of the code review pipeline. Takes merged finder
output, re-reads the cited source for every claim, and marks each finding
confirmed / refuted / uncertain.
---
You are the **verifier** in a multi-stage code review pipeline. The finders
(`find-bug`, `find-security`, `find-performance`, `find-quality`) produced a
combined list of findings. Your single job is to **re-read the cited source
for every finding and decide whether the claim is real**.
Finders can hallucinate. They can cite the wrong line. They can describe code
that doesn't exist. They can claim a pattern is broken when it's actually safe
in context. Your job is to catch all of that before the report reaches the
user.
## Input format
You receive a JSON object:
{
"findings": [
{
"id": "bug-1",
"severity": "critical",
"location": "path/to/file.py:123",
"claim": "...",
"evidence": "...",
"fix": "...",
"finder": "bug"
}
]
}
The ids are namespaced by finder (`bug-N`, `sec-N`, `perf-N`, `q-N`) and
unique across the input.
## Verification process
For **each** finding:
1. **Read the file at the cited location.** Use `read_file` with a line
window that covers `evidence` plus \~20 lines of context above and below.
2. **Check that `evidence` matches source literally.** Whitespace-insensitive,
but character-for-character. A quote that doesn’t appear in the source is
an automatic refute.
3. **Check that the `claim` is supported by the evidence.** Ask:
* Is the described behavior actually happening? (E.g. “unescaped user
input” — is the input user-controlled, and is the sink actually
unescaped?)
* Is the context the finder assumed correct? (E.g. “N+1 query” — is the
enclosing code actually a loop over user-scale data, or is it called once
at startup?)
* Does upstream / downstream code already mitigate the issue? (E.g.
validation at a middleware layer.)
4. **Emit a verdict** for each finding:
* **`confirmed`** — evidence matches source AND claim follows from evidence
AND no upstream mitigation invalidates it.
* **`refuted`** — evidence doesn’t match source, OR claim contradicts the
surrounding code, OR clear upstream mitigation nullifies the concern.
* **`uncertain`** — claim is plausible but confirming requires reading code
outside the provided diff/source scope (e.g., “this depends on whether
the caller validates X”). Include a short `uncertain_reason`.
## Verification budget
* Read each file **at most once per session**, using a window wide enough to
cover all findings in that file. Cache between findings from the same file.
* Do not chase references outside the files cited in findings unless a
finding’s verdict genuinely depends on it — in which case, prefer `uncertain`
to going deep.
* You have a budget: refute aggressively on weak claims, confirm only on
strong evidence. The user sees only confirmed findings (and optionally
uncertain) in the final report.
## Output format
Return **JSON only** — no prose, no markdown. Single object matching:
{
"verified": [
{
"id": "bug-1",
"verdict": "confirmed|refuted|uncertain",
"reason": "Short (≤200 chars) explanation of verdict — quote source if refuting.",
"uncertain_reason": "Only present when verdict is uncertain; what extra context would be needed to decide.",
"severity_override": "critical|major|minor|nit",
"severity_override_reason": "Only present when you think the finder's severity was wrong; short justification."
}
]
}
Rules:
* One entry per input finding, keyed by `id`.
* `verdict` is always one of the three values.
* `reason` is mandatory for every entry. Refuted findings must cite what the
source actually shows.
* `severity_override` is OPTIONAL — only include if the severity was clearly
wrong (e.g. finder called a nit “critical”). Dedupe uses your override if
present.
* Do NOT re-emit the finding body — dedupe already has it. Only the verdict.
## Anti-patterns in verifier output
* Confirming a finding you didn’t actually re-read → user sees hallucinated
bugs
* Refuting confidently based on partial context → user misses real bugs
* Over-using `uncertain` → dedupe passes them through and the report becomes
noise; use only when you genuinely can’t tell
* Severity overrides that relitigate taste — only override on factual
mis-tiering
Your verified output feeds into `code-review-dedupe`, which merges overlap and
produces the final report. Confirmed findings surface to the user; refuted
findings are dropped silently; uncertain findings surface with an `[uncertain]`
tag.
+192
View File
@@ -0,0 +1,192 @@
---
name: code-review
description: >
Multi-stage code review pipeline — 4 parallel finders (bug, security,
performance, quality) → verify → dedupe → synthesize → final markdown
report. Load into a workstream to review a diff; the model orchestrates
the pipeline by spawning sub-skills as task calls.
---
# Code Review — multi-stage pipeline
You are the **orchestrator** of a multi-stage code review pipeline. Your job
is to coordinate the stages below and produce a final markdown report.
## Pipeline
find-bug ─┐
find-security ─┤
├──► verify ──► dedupe ──► synthesize ──► final markdown
find-performance ─┤ (claim (merge + (fix-verify
find-quality ─┘ check) filter) + render)
(parallel)
## Step 1 — Determine scope
Figure out what to review. Options:
- **Uncommitted changes** — run `git diff` in the repo root
- **Branch range** — `git diff origin/master..HEAD`
- **Specific files** — read them directly
If the user didn't specify, default to uncommitted changes + last commit.
Ask for clarification only if it's truly ambiguous.
Compute the diff once and write it to a temp file (e.g. `git diff origin/main..HEAD > /tmp/review.diff`)
so all finders operate on the same bytes.
## Step 2 — Spawn the four finders in parallel
**In a single message, make four concurrent subagents using `task` calls** -
one per finder, each prompted with its finder skill. Parallel invocation is
the key to wall-clock speed; serial spawning defeats the design.
Prompt: minimal; the path to the diff file (e.g. `/tmp/review.diff`),
the skill to use (e.g. `code-review-find-bug`), plus a one-paragraph
context block about what the change is trying to accomplish. The skill
body itself carries the search protocol, severity calibration, lane rules,
and JSON output schema — don't re-state them in the prompt.
Example prompt:
Load the `code-review-find-bug` skill and review `/tmp/review.diff`.
The change adds feature XYZ, and does blah blah blah.
| Finder | Skill |
|--------|------------------|
| Bug | `code-review-find-bug` |
| Security | `code-review-find-security` |
| Performance | `code-review-find-performance` |
| Quality | `code-review-find-quality` |
Do NOT tell a finder what the other finders are doing. Each works in
isolation; the dedupe stage handles overlap.
Do NOT load the skills yourself.
## Step 3 — Merge finder output
Each finder returns a JSON object:
{
"finder": "bug|security|performance|quality",
"findings": [
{"id": "...", "severity": "...", "location": "...",
"claim": "...", "evidence": "...", "fix": "..."}
]
}
Build the combined list: concatenate all four `findings` arrays, stamping each
entry with its finder name. Ids are already namespaced (`bug-N`, `sec-N`,
`perf-N`, `q-N`) — no renumbering.
If a finder returned an empty string, malformed JSON or errored, this is a BUG.
Run it again, but if it fails a second time, abort the entire review process.
If the combined list is empty (all finders returned `{"findings": []}`),
skip steps 4–6 and emit a short “Approve — no issues found” report directly.
## Step 4 — Spawn verify (serial)
One `task` call with skill `code-review-verify`. Pass the skill and the merged
findings list as JSON in the prompt. The skill body carries the verifier’s
identity and instructions. Example prompt:
Load the `code-review-verify` skill.
---
{
"findings": [
/* findings goes here */
]
}
The verifier returns:
{
"verified": [
{"id": "...", "verdict": "confirmed|refuted|uncertain",
"reason": "...", "severity_override": "..."}
]
}
## Step 5 — Spawn dedupe (serial)
One `task` call with skill `code-review-dedupe`. Pass BOTH the
original merged findings AND the verifier’s verdicts in the prompt as a
single JSON object with `findings` and `verdicts` keys. Example:
Load the `code-review-dedupe` skill.
---
/* json goes here */
Dedupe drops refuted findings, merges overlap, applies severity overrides,
and emits:
{
"deduped": [
{
"ids": ["bug-1", "sec-1"],
"dimensions": ["bug", "security"],
"severity": "critical",
"location": "...",
"claim": "...",
"evidence": "...",
"fix": "...",
"verdict": "confirmed|uncertain",
"uncertain_reason": null
}
],
"stats": {
"input_findings": 12,
"refuted": 4,
"after_clustering": 6,
"by_severity": {"critical": 1, "major": 2, "minor": 2, "nit": 1},
"uncertain": 1,
"finder_failures": []
}
}
## Step 6 — Spawn synthesize (serial)
One `task` call with skill `code-review-sanity`. Pass dedupe’s JSON
output verbatim in the prompt.
Synthesize re-reads source for each finding’s `fix`, marks each as
`unchanged` / `refined` / `unverifiable`, then renders the final user-facing
markdown.
## Step 7 — Return the report
Emit synthesize’s markdown verbatim to the user. Do NOT wrap it in commentary
— the report is self-contained.
If synthesize itself errored or returned nothing, fall back to a minimal
report built from dedupe’s JSON, prepended with:
> ⚠ Pipeline note: synthesize did not complete normally; the report below
> skips fix-verification.
## Parallelism and cost
* Wall clock ≈ max(finders) + verify + dedupe + synthesize. Finders run in
parallel; the rest is serial.
* Token cost ≈ 6–8× a single-pass review. Deliberate — verify removes
hallucinated claims, synthesize tightens fixes before they reach the user.
* If the diff is tiny (≤50 lines, single file, no security/perf surface) the
full pipeline is overkill; in that case spawn only `find-bug` +
`find-quality` and still run verify + dedupe + synthesize. Default to the
full pipeline when in doubt.
## Short pipeline variant
When the diff is small (≤50 lines, single file, no security/perf surface):
* Spawn only `find-bug` + `find-quality` in parallel
* Still run verify + dedupe + synthesize
* Half the token cost, same rigor on the dimensions that matter for small
changes
+138
View File
@@ -0,0 +1,138 @@
---
name: diagnosing-bugs
description: Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.
---
# Diagnosing Bugs
A discipline for hard bugs. Skip phases only when explicitly justified.
When exploring the codebase, read `CONTEXT.md` (if it exists) to get a clear mental model of the relevant modules, and check ADRs in the area you're touching.
## Redact
This skill has you show commands, outputs and captured artifacts. **Redact every secret first**: write `<REDACTED>` in its place. Build loops against env vars, so the credential stays in the environment rather than in what you show. Captured artifacts carry auth headers: quote only the lines that carry the signal.
If the redacted output is not enough to diagnose the bug, say so and ask the user.
## Phase 1: Build a feedback loop
**This is the skill.** Everything else is mechanical. If you have a **tight** pass/fail signal for the bug (one that goes red on _this_ bug), you will find the cause; bisection, hypothesis-testing, and instrumentation all just consume it. If you don't have one, no amount of staring at code will save you.
Spend disproportionate effort here. **Be aggressive. Be creative. Refuse to give up.**
### Ways to construct one, in roughly this order
1. **Failing test** at whatever seam reaches the bug: unit, integration, e2e.
2. **Curl / HTTP script** against a running dev server.
3. **CLI invocation** with a fixture input, diffing stdout against a known-good snapshot.
4. **Headless browser script** (Playwright / Puppeteer) that drives the UI and asserts on DOM/console/network.
5. **Replay a captured trace.** Save a real network request / payload / event log to disk; replay it through the code path in isolation.
6. **Throwaway harness.** Spin up a minimal subset of the system (one service, mocked deps) that exercises the bug code path with a single function call.
7. **Property / fuzz loop.** If the bug is "sometimes wrong output", run 1000 random inputs and look for the failure mode.
8. **Bisection harness.** If the bug appeared between two known states (commit, dataset, version), automate "boot at state X, check, repeat" so you can `git bisect run` it.
9. **Differential loop.** Run the same input through old-version vs new-version (or two configs) and diff outputs.
10. **HITL bash script.** Last resort. If a human must click, drive _them_ with `scripts/hitl-loop.template.sh` so the loop is still structured. Captured output feeds back to you.
Build the right feedback loop, and the bug is 90% fixed.
### Tighten the loop
Treat the loop as a product. Once you have _a_ loop, **tighten** it:
- Can I make it faster? (Cache setup, skip unrelated init, narrow the test scope.)
- Can I make the signal sharper? (Assert on the specific symptom, not "didn't crash".)
- Can I make it more deterministic? (Pin time, seed RNG, isolate filesystem, freeze network.)
A 30-second flaky loop is barely better than no loop; a 2-second deterministic one is tight, a debugging superpower.
### Non-deterministic bugs
The goal is not a clean repro but a **higher reproduction rate**. Loop the trigger 100×, parallelise, add stress, narrow timing windows, inject sleeps. A 50%-flake bug is debuggable; 1% is not, so keep raising the rate until it's debuggable.
### When you genuinely cannot build a loop
Stop and say so explicitly. List what you tried. Ask the user for: (a) access to whatever environment reproduces it, (b) a redacted captured artifact (HAR file, log dump, core dump, screen recording with timestamps), or (c) permission to add temporary production instrumentation. Do **not** proceed to hypothesise without a loop.
### Completion criterion: a tight loop that goes red
Phase 1 is done when the loop is **tight** and **red-capable**: you can name **one command** (a script path, a test invocation, a curl) that you have **already run at least once** (show the invocation and its output, redacted), and that is:
- [ ] **Red-capable**: it drives the actual bug code path and asserts the **user's exact symptom**, so it can go red on this bug and green once fixed. Not "runs without erroring"; it must be able to _catch this specific bug_.
- [ ] **Deterministic**: same verdict every run (flaky bugs: a pinned, high reproduction rate, per above).
- [ ] **Fast**: seconds, not minutes.
- [ ] **Agent-runnable**: you can run it unattended; a human in the loop only via `scripts/hitl-loop.template.sh`.
If you catch yourself reading code to build a theory before this command exists, **stop: jumping straight to a hypothesis is the exact failure this skill prevents.** No red-capable command, no Phase 2.
## Phase 2: Reproduce + minimise
Run the loop. Watch it go red as the bug appears.
Confirm:
- [ ] The loop produces the failure mode the **user** described, not a different failure that happens to be nearby. Wrong bug = wrong fix.
- [ ] The failure is reproducible across multiple runs (or, for non-deterministic bugs, reproducible at a high enough rate to debug against).
- [ ] You have captured the exact symptom (error message, wrong output, slow timing) so later phases can verify the fix actually addresses it.
### Minimise
Once it's red, shrink the repro to the **smallest scenario that still goes red**. Cut inputs, callers, config, data, and steps **one at a time**, re-running the loop after each cut, and keep only what's load-bearing for the failure.
Why bother: a minimal repro shrinks the hypothesis space in Phase 3 (fewer moving parts left to suspect) and becomes the clean regression test in Phase 5.
Done when **every remaining element is load-bearing**: removing any one of them makes the loop go green.
Do not proceed until you have reproduced **and** minimised.
## Phase 3: Hypothesise
Generate **3–5 ranked hypotheses** before testing any of them. Single-hypothesis generation anchors on the first plausible idea.
Each hypothesis must be **falsifiable**: state the prediction it makes.
> Format: "If <X> is the cause, then <changing Y> will make the bug disappear / <changing Z> will make it worse."
If you cannot state the prediction, the hypothesis is a vibe: discard or sharpen it.
**Show the ranked list to the user before testing.** They often have domain knowledge that re-ranks instantly ("we just deployed a change to #3"), or know hypotheses they've already ruled out. Cheap checkpoint, big time saver. Don't block on it; proceed with your ranking if the user is AFK.
## Phase 4: Instrument
Each probe must map to a specific prediction from Phase 3. **Change one variable at a time.**
Tool preference:
1. **Debugger / REPL inspection** if the env supports it. One breakpoint beats ten logs.
2. **Targeted logs** at the boundaries that distinguish hypotheses.
3. Never "log everything and grep".
**Tag every debug log** with a unique prefix, e.g. `[DEBUG-a4f2]`. Cleanup at the end becomes a single grep. Untagged logs survive; tagged logs die.
**Perf branch.** For performance regressions, logs are usually wrong. Instead: establish a baseline measurement (timing harness, `performance.now()`, profiler, query plan), then bisect. Measure first, fix second.
## Phase 5: Fix + regression test
Write the regression test **before the fix**, but only if there is a **correct seam** for it.
A correct seam is one where the test exercises the **real bug pattern** as it occurs at the call site. If the only available seam is too shallow (single-caller test when the bug needs multiple callers, unit test that can't replicate the chain that triggered the bug), a regression test there gives false confidence.
**If no correct seam exists, that itself is the finding.** Note it. The codebase architecture is preventing the bug from being locked down. Flag this for the next phase.
If a correct seam exists:
1. Turn the minimised repro into a failing test at that seam.
2. Watch it fail.
3. Apply the fix.
4. Watch it pass.
5. Re-run the Phase 1 feedback loop against the original (un-minimised) scenario.
## Phase 6: Cleanup
Required before declaring done:
- [ ] Original repro no longer reproduces (re-run the Phase 1 loop)
- [ ] Regression test passes (or absence of seam is documented)
- [ ] All `[DEBUG-...]` instrumentation removed (`grep` the prefix)
- [ ] Throwaway prototypes deleted (or moved to a clearly-marked debug location)
- [ ] The hypothesis that turned out correct is stated in the commit / PR message, so the next debugger learns
+49
View File
@@ -0,0 +1,49 @@
---
name: gitea
description: Provides information and workflows for interacting with Gitea.
license: MIT
compatibility: opencode
metadata:
audience: agents
workflow: gitea-development
---
## What Gitea is
Gitea is a self-hosted Git service that is very similar to GitHub.
## Important Notes
- **Do NOT use gitea.com**: We self-host Gitea at `git.nubo.sh`.
- **Workflow**: The workflow is much the same as GitHub (issues, PRs, etc.).
## How to use the `tea` CLI
Use the `tea` command to interact with Gitea.
- `tea notifications`: Check for new notifications.
- `tea issue <number>`: Read an issue.
- `tea issue --comments <number>`: Read an issue and its comments.
- `tea comment <number> "message"`: Comment on an issue.
- `tea pulls`: Manage pull requests.
- `tea pulls create`: Create a pull request.
- `tea pulls <number>`: Read a pull request (e.g., `tea pulls 6`).
- `tea pulls --comments <number>`: Read a pull request and its comments.
- **Marking as Read**: Use `tea notifications read` to mark notifications as read.
- **When to mark as read**: Only mark notifications as read once you have actively addressed the underlying task or review.
## Development Workflows
- **Issues**: Use `tea issue` to track tasks and bugs. Before closing an issue, ensure the relevant Pull Request has been merged.
- **Pull Requests**: Use `tea pulls` to manage and review code changes.
- **Communication**: Use `tea comment <number> "message"` to communicate with project maintainers (e.g., `@hulthe`).
## Responsibility
- **Don't Drop the Ball**: When assigned a task or receiving a review comment, you MUST address it before moving on to other things. Ensure all follow-up actions ( (like replying to a comment) are completed.
## Tips for Agents
- **Stay Updated**: Regularly run `tea notifications` to check for new issues or PR reviews.
- **Communicate**: If confused, use `tea comment <number> "message"` and mention the maintainer (e.g., `@hulthe`).
- **Clean History**: Use `git rebase` and squash commits before submitting a PR to keep the history clean.
- **Use Tea**: Use the `tea` CLI for a faster, terminal-based workflow.
- **Explicitly Ping**: When replying to a comment, use the `@` sign to explicitly ping or mention the person you are replying to. This ensures they receive a notification.
- **CI / Actions**: Gitea uses `.gitea/workflows/` (not `.github/`). Use `tea actions runs` to list runs; `tea actions runs logs <id>` to inspect. A `Status: completed` does NOT mean success — always read the log tail for `Job succeeded` or `Job failed`. The Alpine runner requires `node` before `actions/checkout` and uses package names `rust-clippy` / `rustfmt` (not `clippy`).
## Assignment
- **Tracking Assignments**: Use `tea issue --assignee <your-username>` to see issues assigned to you. Also use `tea pulls --assignee <your-username>` for pull requests.
- **Checking Work**: Regularly check your notifications and assigned issues to ensure you are up to date with your tasks.
+28
View File
@@ -0,0 +1,28 @@
---
name: grilling
description: Grill the user relentlessly about a plan, decision, or idea. Use when the user wants to stress-test their thinking, or uses any 'grill' trigger phrases.
---
Interview the user relentlessly until you reach a shared understanding. Map this as a **design tree**: every decision branches into the decisions that hang off it.
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled: the questions you can ask _now_ without guessing at answers you haven't heard yet. Ask the whole frontier in one round: number each question and give your recommended answer. Then wait for the user's answers before the next round.
Format a round like so:
```
❓ **Q1** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
➡ <your recommended answer>
---
❓ **Q2** - **<question title>**: <question body, might be multiple paragraphs, including multiple choices>
➡ <your recommended answer>
```
Each round the user answers reshapes the tree: settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a _later_ round, not this one.
Finding _facts_ is your job, never the user's. When a frontier question needs a fact from the environment (filesystem, tools, etc.), dispatch a sub-agent to find it; don't ask the user for anything you could look up yourself. Don't block on it: a running exploration is an unsettled prerequisite, so only the questions downstream of it wait for the sub-agent to report; ask the rest of the frontier now. The _decisions_ are the user's: put each to them and wait.
The session is done when the frontier is empty: every branch of the design tree visited, nothing left silently assumed. Do not act on it until the user confirms you have reached a shared understanding.
+84
View File
@@ -0,0 +1,84 @@
---
name: rust-best-practices
description: >
Best-practices for Rust software. Load into a workstream whenever you need to read or write a Rust (.rs) file.
---
# Best practices for programming Rust
- Run `cargo check`, `cargo clippy`, and `cargo clippy` before comitting to git.
- Don't ignore warnings from cargo.
- Don't supress warnings using e.g. `#[allow(warning)]`
- Never use `unsafe` without asking the user for explicit permission.
When setting up a new rust project, enable these lints:
```toml
[lints.clippy]
allow_attributes = "warn"
as_ptr_cast_mut = "warn"
as_underscore = "warn"
borrow_as_ptr = "warn"
implicit_clone = "warn"
undocumented_unsafe_blocks = "warn"
unicode_not_nfc = "warn"
unused_async = "deny"
wildcard_dependencies = "deny"
[lints.rust]
absolute_paths_not_starting_with_crate = "deny"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "deny"
missing_abi = "deny"
non_ascii_idents = "forbid"
rust_2018_idioms = { level = "deny", priority = -1 }
single_use_lifetimes = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
```
## Newtypes and "Parse, Don't Validate"
Use newtypes to make invalid states unrepresentable. Instead of passing raw strings and validating at every use site, parse once into a validated type.
### Pattern
```rust
pub struct Email(String); // private field prevents construction without validation
impl Email {
pub fn parse(s: &str) -> Result<Self, ParseError> {
if !s.contains('@') {
return Err(ParseError::InvalidEmail);
}
Ok(Self(s.to_owned()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
```
### Guidelines
- **Private fields**: Force construction through `parse()` only
- **Parse at boundaries**: Validate at system edges (API handlers, CLI args, config loading)
- **Use the type in signatures**: `fn create_user(email: Email)` not `fn create_user(email: &str)`
- **No re-validation**: Once parsed, the type guarantees validity
### When to Apply
- Email addresses, passwords, phone numbers
- File paths, URLs, identifiers
- Any domain value with invariants
- Configuration values requiring validation
### Anti-pattern to Avoid
```rust
// BAD: validation scattered, easy to forget
fn handler(email: &str) {
if !validate_email(email) { return Err(...); }
create_user(email) // still just &str, could be invalid
}
```