Skip to content
Back to skills

Finetooth

ASecurity

Whole-repository review in blocks — with a file → block coverage map, hypotheses as the second denominator, four agent roles (hunter, verifier, fixer, fix reviewer) and on-disk state that survives session changes. Use when asked for a full or whole-codebase review or audit that must cover every file rather than a diff, to resume a review already in progress (the repository has docs/review/), or to run the hunter, verify, fix or fixreview role on a review block.

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 30, 2026
ai-agentspythongoshellnodegitapisecurityperformance

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 20 files and shows the line behind each finding

Scanned September 30, 2026

npx -y skills add mikey-semy/finetooth --skill finetooth --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Finetooth?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Finetooth
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mikey-semy-finetooth/badge)](https://www.skillsdirectory.com/skills/mikey-semy-finetooth)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: finetooth
description: Whole-repository review in blocks — with a file → block coverage map, hypotheses as the second denominator, four agent roles (hunter, verifier, fixer, fix reviewer) and on-disk state that survives session changes. Use when asked for a full or whole-codebase review or audit that must cover every file rather than a diff, to resume a review already in progress (the repository has docs/review/), or to run the hunter, verify, fix or fixreview role on a review block.
license: MIT
compatibility: Requires git and Python 3 (standard library only; tested on 3.12 and 3.14). Run from the directory of the repository under review.
metadata:
  version: "0.8.0"
  original-author: "Georgiy Khudobandaev (https://github.com/Georgiy-Khudobandaev)"
  source: "https://github.com/mikey-semy/finetooth"
---

# Whole-repository review

A review of all the code, not of a diff: the repository is cut into blocks, every block goes
through a hunter and a verifier, a block with findings to fix also through a fixer, and a
block with any finding `fixed` also through a fix reviewer (`check` refuses to close it
without that report); completeness is proven by the coverage map: not one file without a
block. Sessions change, context runs out, so **all state
lives on disk** in the project's `docs/review/` and is read back by the tool. Keep nothing in
the memory of the conversation.

## The tool

Everything goes through `scripts/review.py` from this skill, **run from the root of the
repository under review** (the root is taken from git by the working directory):

```sh
python3 <path-to-skill>/scripts/review.py status
python3 <path-to-skill>/scripts/review.py version   # which version of the kit this project is on
```

Below it is called `review`. If `docs/review/blocks.json` has a `cli` field, the project calls
the tool its own way — use that. Every hint and every refusal is assembled from it
(`<cli> coverage`, `<cli> restamp H1`), so the value has to be a command that takes the
subcommand and its flags after it: `npm run review --`, a shell wrapper of the project's
own. `make` is not one of them — it reads `--role` as its own option — so a project on
`make` leaves `cli` unset and gets hints with the real path to the tool
([assets/makefile-snippet.mk](assets/makefile-snippet.mk) has the targets for the everyday
commands). Every refusal from the tool names the command that fixes it: read the refusal,
do not guess. Each command's own help lists its flags.

## Getting started

**The repository already has `docs/review/`** — a review is in progress. Do not start a parallel
one of your own:

1. `review status` — where we are and which block is next; `review next` — its id.
2. `review check` — is the state consistent. Red gets fixed first.
3. `docs/review/journal.md` — what was decided before you and why.

**There is no directory** — a review is being set up:

1. `review setup --project <Name>` (plus `--cli "<command>"` if the project calls the tool its
   own way, and `--lang ru` for Russian) — skeleton `blocks.json`, `invariants.md`, the entry
   point `docs/review/README.md`. The `lang` field in `blocks.json` (`en` by default, `ru`)
   selects the language of the prompt templates and assets; the Russian ones sit next to the
   English with a `.ru.md` suffix. When the project's own `.claude/settings*.json` deny a
   gate command or one the roles run (`pytest`, `uv run` …), `setup` names the rule — put
   the working form (`python3 -m pytest`) into the invariants, or lift the rule for the runs.
2. `docs/review/invariants.md` — the rules of THIS project. It is pasted to every agent and
   decides what the agent will count as a defect. Generic words are useless — write what the
   project has already paid for.
3. `docs/review/blocks.json` — `gates` (the commands of the project's gates) and the blocks:
   cross-cutting first, domain next, live-system last. `review inventory` prints the
   repository tree with sizes and ownership — cut by it. A block is what can be read in one
   sitting (the `readable_lines` ceiling, 6000 lines by default; `review sizes` shows who is
   above it). `set-status <ID> hunted` records the size the block was read at and
   refuses a block above the ceiling; one read within it that grows later is a `check`
   warning, and the next review splits it first. A block that reading cannot prove (test
   quality, performance, scanners) gets `"proof": "measured"`: the proof is the artifacts
   from the manifest, and the ceiling does not apply. A criterion that enumerates across the
   program ("every place that changes data") is a sweep, not reading: declare it in `sweep`,
   and the hunter enumerates by a script at `docs/review/sweeps/<ID>.<ext>`, then reads the
   hits. A block without `paths` is a live system. Sample —
   [assets/blocks.example.json](assets/blocks.example.json).
   Only a part on purpose — a trial run, a release gate, one risky area: declare
   `"scope": {"paths": ["src/api/**"], "reason": "why"}` in `blocks.json` instead of
   excluding the rest; coverage is then counted inside the scope, and every report says the
   review is partial.
4. `review coupling` — files that change together (from `git log`) but sit in different
   blocks: the seams nobody reads. For each pair it names the `ref_paths` entry and the
   hypothesis to add to the manifest; several pairs between the same two blocks are a seam
   block waiting to be cut. Shared nodes (a schema, a dictionary) are listed apart.
5. `review order` — the blocks in the order worth walking them: the cost of failure first
   (`risk` on the block: `critical|high|medium|low`; without it the declared order speaks),
   change frequency from `git log` second. A report, not a rewrite: reorder `blocks.json`
   yourself if you agree. Both commands count what landed on the first-parent line, a merge
   as one change (its branch's diff), and print which history they read.
6. `review init`, then `review coverage` — work through the unowned files until there are
   zero. **A human assigns a file to a block**: a file caught by a pattern match will be
   counted as read without having been read.

## Working through a block

1. **Manifest** `docs/review/blocks/<ID>-<slug>.md`: why the block exists, what counts as a
   finding, 10–15 numbered hypotheses about this project, an acceptance criterion that cannot
   be met without reading the code. Sample — [assets/manifest.example.md](assets/manifest.example.md).
   Without project-specific hypotheses the review comes out "on general grounds"; do not cut this part.
   `review seams <ID>` lists the pairs of the block's own files linked by an import (TS/JS
   with the `paths` aliases of `tsconfig.json`/`jsconfig.json`, Python) or by joint changes
   (the thresholds of `coupling`): for each of the top ones write a hypothesis on what one side
   assumes about the other. The hunter prompt carries the same top pairs.
2. **Hunter.** `review set-status <ID> running` first — the fix gate (step 7) stands there:
   while earlier blocks hold open findings at `fix_gate` severity it refuses, and the way on
   is to fix, defer with a reason or reject them, never to skip the status. Then
   `review prompt <ID> --role hunter` prints a ready prompt — hand it to a subagent **whole
   and unedited**. The agent writes the report and the draft findings to disk itself. Then
   `review set-status <ID> hunted`.
3. **Import.** `review import <ID>` takes the draft into the register, so the verifier gets
   the hunter's findings as recorded ones. The plain import refuses when the file would erase
   a finding already recorded against the block or overturn a recorded decision — then
   `import <ID> --append` (or `--force`, deliberately).
4. **Verifier** — a different agent: `review prompt <ID> --role verify`. Checks every finding
   by execution, does its own pass over the most dangerous places. Its verdicts on recorded
   findings come as a table with one `set-finding` command per change (`--severity`,
   `--confidence`, `rejected --reason`, `duplicate --dup-of`) — run them. Rejected findings
   are not deleted — they stay with the reason. The draft then holds only the verifier's new
   findings: `review import <ID> --append`. (Both roles check their draft first with
   `review import <ID> --dry-run`: every row as `import` would write it, asked by `check`'s
   own gates, nothing written — their templates say so.) Then `review set-status <ID> verified`:
   `verified` and `closed` are refused while the draft holds rows the register does not, and
   `check` refuses the same for a block past verification — unimported findings are
   invisible to the summary and the fix gate. (Importing only after the verifier works too:
   then its file is final, and a plain `import` takes it.)
5. **Acceptance.** Read both reports yourself and check them against the acceptance criterion.
   `review hypotheses <ID>` shows which of the block's hypotheses got a verdict and where.
   Coverage incomplete — the block goes back for another pass, not to closure.
   `review findings` regenerates `findings.md`, `review check` must be green.
   A class with a third instance is a warning until the fix phase (`root/guard-due`): the
   guard is the fixer's, and `check` refuses once an instance is fixed or its block is `fixing`.
6. **Journal.** `review log <ID> "what was decided and why"` — right away: this cannot be recovered.
7. **Fixing** — only when the block has confirmed findings to fix (none — go to step 9 after
   acceptance); yet another agent: `review prompt <ID> --role fix [--round N]`. The prompt
   hands the fixer at most three findings a run, one commit per finding, under the project's
   own commit rules (DCO and the like, found in its files). Cut assignments by related areas,
   not one finding at a time. A repeat round needs `--round N`: without it the second fixer
   writes over the first one's report, and the fix reviewer of round N is pointed at
   `<ID>-<slug>.fix-N.md`, which nothing would have written. Run the gates and the revert
   check yourself after the fixer. Findings are moved with
   `review set-finding <ID…> fixed --commit <sha>` (several ids at once; `--fixed-in <path>`
   when the fix is in another file); a defect class with a third instance is closed by a
   guard (`--rule <path to the test or rule>`), not by a list of fixes. The guard is recorded
   only on the findings named in the command — name the instances it goes red on,
   `--clear-rule` takes it off one it does not; `review roots` lists the classes, their
   instances and which guard each instance carries, and flags a root whose instances disagree.
   Deferring is allowed only with a reason (`deferred --reason`), and a deferred finding
   leaves the review as an accepted risk, published in the summary with that reason.
   **The fix gate:** `review set-status <next ID> running`
   refuses while findings at `fix_gate` severity or above (`high` by default, set in
   `blocks.json`; `"none"` switches it off) are open in the blocks already passed — the
   method finds faster than a project fixes, and a finding that never reaches a fix is debt.
   `review status` shows this debt as its own line. Hunting every block first and fixing
   afterwards is a strategy, not a defect of the gate: `"fix_gate": "none"`, paid for by
   findings whose code moves while they wait (restamps) and by a class found again in every
   later block instead of being closed by a guard first.
8. **Fix reviewer** — required once any finding of the block is `fixed`; a fresh agent that
   did not write the fixes:
   `review prompt <ID> --role fixreview --diff main...HEAD [--round N] [--scope <half>]`.
   The diff is pasted into the prompt whole, and `--scope` names a reviewer's half in its
   report without shrinking it: a diff too large for one agent is split by giving each a
   narrower `--diff` range. Its
   confirmed findings go into the register as a top-up import, even the ones already fixed,
   with the command its prompt names: `import <ID> --append --round N --diff <base>..<tip>`
   records which review found them (`found_in`). A new round only for a finding of medium or
   higher; low ones are fixed by the fixer or by the lead, and a lead's fix is marked in the
   journal and the PR as having no independent review. **The loop signal:** when the top
   finding of review N−1 (medium or higher) lies on a line fix round N−1 wrote, by the lines of
   its diff, `prompt --role fix --round N` refuses and `check` warns — another round would fix
   its own last fix. The next move is a human's: a different mechanism, a revert of the class
   to its last strict state, or closing the block. Record it with
   `review decide <ID> "<decision>"` (it goes to the journal and into the next fix and fix
   review prompts, and lifts the refusal); a decision to close is carried out with `set-status`.
   Give each round its own `--diff` range and the signal means "this round".
9. Only after that `review set-status <ID> closed`: without a fix reviewer's report a block
   with fixes cannot be closed.

**Headless, with the spend measured:** `assets/run-role.sh <ID> <role>` (`hunter`, `verify`,
`fix`, `fixreview`) runs the role through `claude -p` and writes the spend to the journal; the
turn cap is twice what the first measured run of the role needed (`ROLE_MAX_TURNS`
overrides it). Its per-role tool lists pre-approve, they do not restrict — what a run must not
touch goes into `ROLE_DENY`, which the script hands to `claude -p` as its deny list. It will not start `fix` or
`fixreview` on a dirty tree (`ALLOW_DIRTY=1` to insist), and the agent's PID sits in
`<stream>.pid` while it runs, so a stop reaches it.

**An interrupted role is started again, not continued.** A role's cost is turns × context, and
the whole context is sent again on every turn; the prompt cache makes that cheap only for
minutes. An agent continued after a pause — a usage limit, a crash, the night — sends its whole
context again at full price: in a field run (29.09, reported by the operator) 5–8 agents of
500–800k tokens each, continued after the plan's limit, took 15–20% of a five-hour window within
a minute. Everything a role needs is on disk — the manifest, its draft, the reports — so a role
cut off is started anew on its block (`run-role.sh` never continues a session), and it reads the
block again for a fraction of that. Run as many roles at once as the plan's window holds, not
as many as the machine does — two or three, and only one that writes (`fix`, `fixreview`) per
checkout: hunters and verifiers only read, two writers in one tree commit each other's edits,
so a second writer gets a worktree of its own. A role whose context passes a few hundred thousand
tokens is reading outside its block or keeping test output, and its report says which.

## Rules not to break

- One agent does not hunt and fix at the same time; the one who found does not fix; the one
  who fixed does not verify. A repeat fix goes to a fresh agent, not the same one: the
  assignment is self-contained, and the first attempt's mistakes are mistakes of attention.
- A block boundary is a full stop: report to the owner and wait for the go-ahead on the next
  one. An open question is repeated in full, each with who decides and a recommendation.
- A block is not closed without the acceptance criterion met and without the verifier's report.
- Checking outside your own repository — against a fresh `origin` after `git fetch`: a stale
  tree shows what is fixed as broken.
- No references to the review in code: finding and block numbers die with `docs/review/`.
  `review refs` lists the ones that got in; `check` warns about them.
- Do not run more than two or three agents at once: the plan's usage window runs out long
  before the machine does, and a build on the machine makes it fewer still.

## What `review check` holds

A red check means the work is not done, even if it looks done. Among other things it catches:
a file without a block and a stale coverage map; a file of a readable block not named by full
path in any report (what was read — as a list, what was not — in the coverage limits); a
hypothesis without a verdict or with conflicting verdicts; a hypothesis confirmed by the
hunter, the fixer or the verifier whose verdict names no finding of the draft or the register (a
confirmed hypothesis is a finding; what is not a defect is "refuted"); a hunter report without a
"Coverage limits" section and an empty verifier report; a deferred finding without a reason;
an open finding older than a week (a warning);
a block in `blocked` without a note; phases out of order; a block closed with fixes but
without a fix review; a block and a finding closed on a different version of the code
(fingerprints — `review restamp` if the changes are unrelated, `review backfill` for records
older than the fingerprints; a finding's fingerprint is the lines around its line, so only an
edit there fails; an edit above it that only moved it is not reported — findings.md, SARIF,
the summary and the prompts show the line it sits on now, `review restamp <ID>` records it,
`--line <N>` re-anchors a defect that now sits elsewhere, `--file <path>` one whose code
moved to another file); a finding without a rejection
reason, a fix commit that does not touch the file, a duplicate of a nonexistent finding, a
guard at a nonexistent path; a draft left outside the register; a scope without a reason; a
tree more than a week behind the server; the loop signal without a recorded decision (a
warning).

## In CI and on the platform

`review check` is the gate a project runs in CI and makes required: a merge that edits code
under an open finding, or leaves the register contradicting the tree, stays red.
`review sarif` prints the open and deferred findings as SARIF 2.1.0 (`--out <file>` writes
it instead) for GitHub code scanning — the findings show in the Security tab and on the
lines of a pull request; a deferred one is marked as an accepted risk. Ready jobs:
[assets/github-actions-snippet.yml](assets/github-actions-snippet.yml) (`check` plus the
SARIF upload, and optionally the findings as pull request review comments through
reviewdog) and [assets/gitlab-ci-snippet.yml](assets/gitlab-ci-snippet.yml) (`check`, and
the findings as merge request discussions through reviewdog on any tier — GitLab shows SARIF
on Ultimate only).

## When the review is finished

All blocks `closed`, no open findings, every rejected one has a reason and every deferred
one — a deferral is an accepted risk that leaves the review with its reason, not an
unfinished fix. Then
`review summary` writes the one file that outlives the directory (`docs/review-summary.md`
by default, `--out` elsewhere): the date and the base commit, the blocks and their acceptance
criteria, the rejected findings with reasons, the accepted risks, what closed each defect
class, what the role runs cost. `review summary --html` writes the same summary as one
self-contained HTML file (`docs/review-summary.html`; tables, SVG charts, no network) for a
reader who never saw the review. Only then
the `docs/review/` directory **is deleted whole in one change**, and what lasts moves out:
rules into the root instructions file, decisions into ADRs, checks into tests. Later,
`review summary --aged docs/review-summary.md` says how far each block has drifted since the
base commit — the only thing a re-run needs to start from. Without the summary the next
review starts from zero.

## Files of the skill

- [references/hunter.md](references/hunter.md), [references/verify.md](references/verify.md),
  [references/fix.md](references/fix.md), [references/fixreview.md](references/fixreview.md)
  — the role templates. `review prompt` assembles the prompt from them; read them only to
  understand or adjust a role. A project may keep its own version in
  `docs/review/prompts/<role>.md` — then that one is used. The Russian versions sit next to
  them as `<role>.ru.md`.
- [references/lessons.md](references/lessons.md) — the lessons of two reviews the rules grew
  out of: read before the first block.
- [assets/](assets/) — samples: blocks, manifest, invariants, journal, banner for the root
  instructions file, `make` and `package.json` targets, CI jobs for GitHub Actions and
  GitLab, a guard example;
  [assets/run-role.sh](assets/run-role.sh) — a role run through `claude -p` with the event
  stream kept and the spend logged.
- [scripts/axes.py](scripts/axes.py) — the spend of one run by axis (cache, turns, tool
  output, re-reads) from that stream; `--journal` gives the one line `run-role.sh` writes,
  `--reply` the agent's answer. It is the only reader of the stream: a killed run leaves
  its last line half-written, and a second parser dies on it.

Files in this skill

  • LICENSE1.1 KB
  • SKILL.md20.5 KB
  • assets/agent-banner.md1.3 KB
  • assets/agent-banner.ru.md2.1 KB
  • assets/blocks.example.json5.1 KB
  • assets/entry-point.md10.4 KB
  • assets/entry-point.ru.md16.3 KB
  • assets/github-actions-snippet.yml4.3 KB
  • assets/gitlab-ci-snippet.yml4.3 KB
  • assets/guard-grep.sh4.3 KB
  • assets/invariants.example.md2.6 KB
  • assets/invariants.example.ru.md4.5 KB
  • assets/journal.example.md1.9 KB
  • assets/journal.example.ru.md3.1 KB
  • assets/makefile-snippet.mk2.5 KB
  • assets/manifest.example.md5.1 KB
  • assets/manifest.example.ru.md7.9 KB
  • assets/package-json-snippet.json588 B
  • assets/run-role.sh9.7 KB
  • references/fix.md11.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…