Skip to content

Review at scale

The conclusion first

Reviewing agent output line by line does not scale. Reviewing its structure does. explain is the half of this tool that produces the structure, and it is the only command that never blocks anything.

check answers "is this allowed". explain answers "what did this do to the shape of the code" — for a human before they open a file, or for an agent that needs to know what its own change touched.

The change summary

$ scitools-hook explain --staged
change summary
  database: /home/you/.cache/scitools-hook/e7cde7af638ed147/after.und

files (1)
  pricing/settle.py  [Directory Structure/pricing]
    added     file     pricing/settle.py
      CountDeclFunction  - -> 1  (+1)
      CountLineCode  - -> 35  (+35)
      MaxCyclomaticStrict  - -> 14  (+14)
      RatioCommentToCode  - -> 0.06  (+0.06)
    added     routine  pricing.settle.settle  line 6
      CountLineCode  - -> 34  (+34)
      CountParams  - -> 5  (+5)
      CountPath  - -> 44  (+44)
      CountStmt  - -> 25  (+25)
      CyclomaticModified  - -> 13  (+13)
      CyclomaticStrict  - -> 14  (+14)
      Essential  - -> 1  (+1)
      MaxNesting  - -> 4  (+4)

dependencies (1)
  Directory Structure/pricing
    added    pricing/settle.py -> pricing/rates.py

largest deltas (10)
   1. pricing.settle.settle (pricing/settle.py)  CountPath  - -> 44  (+44)
   2. pricing/settle.py  CountLineCode  - -> 35  (+35)
   3. pricing.settle.settle (pricing/settle.py)  CountLineCode  - -> 34  (+34)
   4. pricing.settle.settle (pricing/settle.py)  CountStmt  - -> 25  (+25)
   5. pricing/settle.py  MaxCyclomaticStrict  - -> 14  (+14)
   6. pricing.settle.settle (pricing/settle.py)  CyclomaticStrict  - -> 14  (+14)
   7. pricing.settle.settle (pricing/settle.py)  CyclomaticModified  - -> 13  (+13)
   8. pricing.settle.settle (pricing/settle.py)  CountParams  - -> 5  (+5)
   9. pricing.settle.settle (pricing/settle.py)  MaxNesting  - -> 4  (+4)
  10. pricing/settle.py  CountDeclFunction  - -> 1  (+1)

largest values (10)
   1. pricing.settle.settle (pricing/settle.py)  CountPath  44
   2. pricing/settle.py  CountLineCode  35
   3. pricing.settle.settle (pricing/settle.py)  CountLineCode  34
   ...

impact (0)
  none

graphs (0)
  none

open in the Understand GUI: understand /home/you/.cache/scitools-hook/e7cde7af638ed147/after.und

Every entity is labelled added, modified or deleted, with the architecture node it sits in. largest deltas is what to read first on a change you did not write: it is the list of what moved most, ordered, regardless of whether anything broke a rule.

The last line is a command you can paste. It opens the exact database the report was built from, in the Understand GUI, so a reviewer who wants to keep digging does not have to reproduce the analysis.

Selections

Flag Analyses
--staged The index, against HEAD. What the hook runs.
--worktree The working tree, staged or not. What an agent runs while editing.
--all The whole project. No before side, so no ratchet and no pre-existing.
--files PATH Exactly these paths. Repeatable. Bare trailing paths mean the same thing.
--range A..B What happened between two commits. explain only.

--range is the pull-request form:

scitools-hook explain --range "origin/main...HEAD" --graphs --impact --out review/

Graphs and the impact set

$ scitools-hook explain --range HEAD~1..HEAD --graphs --impact --out review/
impact (4)
  pricing.settle.goods_line (pricing/settle.py)  2 total; depth 1: 1, depth 2: 1
  pricing.settle.line_total (pricing/settle.py)  1 total; depth 1: 1
  pricing.settle.service_line (pricing/settle.py)  2 total; depth 1: 1, depth 2: 1
  pricing.settle.settle (pricing/settle.py)  0 total

graphs (5)
  Depends On  pricing/settle.py                            review/pricing_settle_py-ccbe74db8212-Depends_On.svg
  Butterfly   pricing.settle.goods_line (pricing/settle.py)  review/pricing_settle_goods_line-6fa67e526589-Butterfly.svg
  Butterfly   pricing.settle.line_total (pricing/settle.py)  review/pricing_settle_line_total-0f12cee419a5-Butterfly.svg
  Butterfly   pricing.settle.service_line (pricing/settle.py) review/pricing_settle_service_line-106fff5a5eb7-Butterfly.svg
  Butterfly   pricing.settle.settle (pricing/settle.py)      review/pricing_settle_settle-9cd08e5b4901-Butterfly.svg

--out DIR without --graphs is refused rather than ignored.

What a butterfly graph is

One graph per changed routine or class: what calls it on the left, what it calls on the right. This is review/pricing_settle_line_total-0f12cee419a5-Butterfly.svg, exactly as Understand rendered it:

get goods_line ▶ ▶ line_total ▶ service_line settle

settle calls line_total, which calls goods_line and service_line. On a repository the size of one you would actually review, this is the picture that tells you whether a change sits at a leaf or in the middle of everything.

Two kinds are exported, and only two, because they are the two Understand 6.5 will actually render for the entities in question:

Kind Drawn for
Butterfly each affected routine and class
Depends On each affected file

A routine draws Butterfly, Calls and Called By, and refuses Depends On with UnderstandError('Unknown Graph') while writing no file. A refused graph is a warning, not a failure: one target the installed Understand will not render must not cost the reviewer every other graph.

File names are <slug of the long name>-<12 hex of the key digest>-<slug of the graph>.svg. Neither part can contain a path separator, which is what confines the export to the directory you chose.

The impact set

--impact lists what references each changed routine and class, by depth:

pricing.settle.goods_line  2 total; depth 1: 1, depth 2: 1

One entity references it directly and one more at two hops. The depth is output.impact_depth, default 3. An entity is reported at the shallowest depth that reaches it and never again, and the entity the walk started from is never part of its own impact set — reference graphs are full of cycles.

depth = 0 is a legal answer meaning "report nothing".

Sizing

[output]
graphs_max = 20        # per group: routines/classes, and files, counted separately
impact_depth = 3
show_highest = false

graphs_max is applied to each group on its own. Leaving the files unbounded would let a whole-project run draw one graph per file in the repository. Zero draws nothing and opens no database at all.

Output formats

scitools-hook explain --staged --format markdown --output review.md
scitools-hook explain --staged --format json | jq '.largest_deltas[0]'

human, json and markdown. The markdown form is the one to paste into a pull request.

A reviewing workflow

For a large agent-authored branch:

# 1. What moved, structurally, and by how much.
scitools-hook explain --range "origin/main...HEAD" --format markdown --output review.md

# 2. The pictures, for the parts of it that look load-bearing.
scitools-hook explain --range "origin/main...HEAD" --graphs --impact --out review/

# 3. Whether any of it broke a rule, or made an existing problem worse.
scitools-hook check --files $(git diff --name-only origin/main...HEAD)

# 4. Open the database and keep digging, if step 2 raised a question.
understand "$(scitools-hook db path)"

Read the shape first. Open files second, and only the ones the shape made interesting.