Structure

A map of a tangle
is still a tangle.

Drawing a graph of badly organised code produces a picture of badly organised code. Past a certain messiness no amount of colouring, filtering or zooming brings the reading back — readers who can follow one graph and not another are not reacting to size, they are reacting to shape. So Mezzanine scores it.

In Mezzanine 1.3.0 and later. Every block of output below is verbatim from the published 1.4.0 binary, run against the repository you can clone.

Why a second question

“Is this hard to change?” and “can this be followed?” are different questions.

Every metric in Mezzanine until this one answered the first: complexity, coupling, cohesion, the smells, the composite score that sorts a refactor list. None of them answered the second, which is a question about how the files are organised rather than about what is inside them. A folder can be immaculately arranged and full of terrible code, or the exact reverse, and one number moving for both reasons could be acted on for neither.

So shape is computed and reported beside the refactor score, never folded into it. Higher is better here, against the grain of every other number in the tool — which is stated in the type, the JSON, the panel and the agent output rather than left to be inferred.

The measure

Four tiers. Each one is the tier below it plus a single property.

Scored over the graph the canvas actually draws — a folder's immediate children, each subfolder standing as one node. Not every descendant flattened together: that sees more, but it scores a picture nobody looks at, and a reader told “this folder is tangled” has to be able to look at the folder and see the tangle.

  1. 0

    Cyclic

    + nothing — this is the floor

    Children depend on each other in a loop. There is no order to read the drawing in, and no other property means anything measured on top of it.

    Break the loop — usually by moving the shared piece down into a child both can depend on.

  2. 1

    Tangled

    + acyclicity

    Acyclic, so there is an order — but edges jump levels instead of stepping down one at a time. The drawing has no layers to follow.

    Either the intermediate layer is not carrying the traffic it should, or the shortcuts around it are the real design.

  3. 2

    Hierarchical

    + layering

    A clean layered DAG. Readable at this level, and says nothing at all about whether the folders inside it are.

    Readable here. What holds it short of fractal is breadth, merging, the doors, or a folder one level down.

  4. 3

    Fractal

    + breadth, branching, doors, and children that hold the same shape

    The shape holds at more than one zoom level. Few enough children to take in at once, branching rather than merging, entered from outside through few doors, and made of children that are themselves at least hierarchical.

    Nothing to do — the shape holds at every level.

Fractal is recursive by construction. Its last clause is a claim about the children, so a folder cannot earn it while sitting on top of a mess — which is what makes the measure self-similarity across zoom levels rather than one level's opinion. It is also what makes the work order fall out for free: a parent held back by a tangled subfolder cannot move until that subfolder does, so the report lists the deepest actionable folder first.

The gates

Seven bars, and only one of them is a convention.

Six are readings of the drawing. The seventh is a count, and it is the one with a story — see below. Own drawing marks the gates a reader can settle by looking at the picture in front of them, as against the ones answered only by the subfolders, the callers, or the blend; that split decides which folder to work on first.

GateBarWhat it readsOwn drawing
Acyclicity1.00No loop among the children. Anything short of a clean 1.00 leaves the folder cyclic. yes
Layering≥ 0.70The share of edges advancing exactly one level. Deliberately not tree-ness — a diamond scores perfectly, and a transitive shortcut past the middle layer does not. yes
Branching≥ 0.70The share of edges that would survive in a spanning forest. Everything past the first edge reaching a child is a merge: two siblings leaning on the same third one. outside the compliance blendyes
Breadth≤ 8 childrenImmediate children, files and subfolders together. The one gate that is a convention rather than a reading of the drawing, and the only one whose fix moves files instead of edges. outside the compliance blendyes
Entry concentration≥ 0.60How much of the traffic arriving from outside lands on one file — the door. Fixable, but the work is in the callers and is not visible in the drawing being shown.
Child compliance≥ 0.80The mean compliance of the subfolders. This is the recursive gate, and the reason fractal is a claim about self-similarity rather than about one level in isolation.
Compliance≥ 0.85The weighted blend of the four above it. Reported last because it is the blocker only when every gate passed and several terms were a little low — several small shortfalls rather than one clear defect.

Branching and breadth gate the tier and stay out of compliance deliberately, so a folder can blend well and still be held back by one of them. A blend cannot carry the reason: two folders at 0.72 can need opposite fixes, so the tool names the binding constraint instead — why rather than how much.

The number

Seven was the guess. Eight is what the guess cost.

The breadth bar started at seven, taken straight from the span of what a reader holds at once. The decision record said plainly that the project could not validate it: every folder over the bar was already cyclic or tangled, so the gate never fired. It named the evidence that would settle it — “the first codebase to feel it will be one whose wide folders are otherwise clean.”

Two days of use produced exactly that. Once several folders had been taken up the ladder, breadth became the most common blocker among the graphs that were already clean — and one of them, parser/rust/declarations, was a spanning tree: acyclic, properly layered, branching 1.00, every child reached from exactly one parent. It was held off fractal by a single file.

A bar that stops a spanning tree is measuring the wrong thing. The bar is eight. It still refuses the twelve-, eighteen- and thirty-nine-child folders nobody takes in at a glance, and non-trivial fractal folders went from four to five the moment it changed.

Seven survives one level down, as the authoring rule for entities in a file and elements in an entity. The reference repositories below are built to it.

Seven was a guess about readers; eight is what the guess costs when checked.

ADR 0018 — the-breadth-bar-is-eight

The number is evidence-backed rather than asserted, and the evidence is in the file that changed it. The next revision is expected to come from the same place.

Where it comes out

Shape arrives unasked, and in work order.

It is part of the quality report rather than a command you have to know about. The folders short of fractal are split by whether their blocker is in their own drawing, and ranked deepest-first within that — because clearing a deep one can clear a parent's child gate too.

mezz — quality, against its own src/
## Folder shape (40 folders)
6 cyclic · 4 tangled · 16 hierarchical · 14 fractal

### Start here *for shape* — the blocker is in the folder's own drawing (10 of 23 shown)
- [hierarchical] parser/dart/declarations — held back by children reaching
  outside from its middle (out-at-the-bottom 0.50); compliance 0.81, acyclic
  1.00, layered 0.71, branching 0.71, one-door-in 0.50, 6 children
- [cyclic] parser/groovy — held back by a loop among its children (acyclic
  0.83); compliance 0.78, acyclic 0.83, layered 0.59, branching 0.59,
  one-door-in 1.00, 12 children
- [tangled] parser/go — held back by edges skipping levels (layered 0.57);
  compliance 0.83, acyclic 1.00, layered 0.57, branching 0.57, one-door-in
  0.75, 9 children
- [hierarchical] parser/rust — held back by more children than a reader holds
  at once (9 of them); compliance 0.90, acyclic 1.00, layered 0.89, branching
  0.89, one-door-in 0.67, 9 children

### Answered elsewhere — blocked on children, callers or the blend (3)
- [hierarchical] parser/typescript/bodies — held back by too many doors in
  (one-door-in 0.40)

Twenty-two of thirty-nine folders hold their shape; five are cyclic. That is the engine's own tree, unedited — a tool that only ever showed clean output on its own code would be worth less, not more.

The tool

One folder's drawing, and one rung to climb.

A line reading “tangled, layered 0.61” names a defect without naming any of the six edges that caused it. reshape hands over the drawing behind the verdict — the children with their levels, every edge with its reading, the doors and the traffic through them — plus an instruction scoped to exactly one rung of the ladder.

reshape · src/check
## Verdict
**cyclic** — held back by a loop among its children (acyclic 0.20).

- acyclicity 0.20 (must be 1.00 to clear `cyclic`)
- layering 1.00 (needs ≥ 0.70 to clear `tangled`)
- branching 1.00 (needs ≥ 0.70 for `fractal`; NOT part of compliance)
- entry concentration 0.50 (needs ≥ 0.60 for `fractal`) — **2 ways in**,
  which the ratio does not say
- out at the bottom 0.60 (needs ≥ 0.70 for `fractal`; NOT part of
  compliance) — **4 exits from the middle**, which the ratio does not say
- compliance 0.54 (needs ≥ 0.85 for `fractal`)
- 5 immediate children (needs ≤ 8 for `fractal`; NOT part of compliance)

## Your task: cyclic → tangled

Break the loops among these children. 6 of 9 edges run inside one, so the
drawing has no reading order at all — every other property is measured on
top of this one.

**One loop**, 4 children and 6 edges:

- `src/check/mod.rs → src/check/count.rs` — carries 1 entity: `violations`
- `src/check/count.rs → src/check/mod.rs` — carries 2 entities:
  `Violation`, `repo_relative`
- `src/check/report.rs → src/check/mod.rs` — carries 3 entities: `Cited`,
  `Outcome`, `Violation`

Cut the cheapest edge that is genuinely a back edge. An edge carrying one
entity is usually a definition sitting in the wrong file, and moving that
definition into a file both ends can depend on breaks the loop without
touching either caller. An edge carrying twenty is the folder's real
structure, and inverting it is a rewrite that will not survive review.

Stop there. The tiers are a ladder and `tangled` is the next rung — the
gates above it are measured on top of this one and are not worth chasing
until it clears.

Every number comes from the analyzer

Measured values off the folder's shape, cut-offs off the thresholds, the drawing off the scoring pass itself. Nothing is re-derived here, so the tool cannot disagree with the panel or with the quality report.

The gate cited is the gate that capped the tier

The blocker has already decided which of the gates it was; this expands that one into the specific files and edges behind it. Picking a different one to talk about would send an agent after something that would not move the verdict.

One rung, never four

A cyclic folder is asked to break its loop and nothing else. The tiers are a ladder, each defined as the one below plus a property — so a four-part instruction is one that cannot be finished, and usually is not started.

In CI

Mezzanine ships no rules of its own.

mezz check grades a tree against rules the project wrote down. Every failure it can produce traces to a line somebody in the checked repo authored, and a repo with no rules file passes and says so.

.mezz/rules.json
{
  "rules": {
    "max_entities_per_file": 7,
    "max_elements_per_entity": 7,
    "max_doors_per_folder": 1
  },
  "exempt": ["**/tests.rs"]
}
~ mezz check
$ mezz check .          # a fresh clone, no rules file
no rules declared in .mezz/rules.json      # exit 0

$ mezz check .          # after declaring the bars above
src/analyzer/mod.rs:1 — max_entities_per_file: 54 declared, bar is 7
src/analyzer/mod.rs:79 — max_elements_per_entity: Analyzer has 34 elements, bar is 7
src/config.rs:13 — max_elements_per_entity: Config has 16 elements, bar is 7
src/diff.rs:1 — max_entities_per_file: 106 declared, bar is 7

Exit 0 when every declared rule holds or none was declared, 1 when one does not, 2 when no verdict could be computed — a gate that passed because it could not understand its own configuration is the failure the command exists to stop.

RuleCounts
max_entities_per_fileDeclarations a reader meets on opening the file, minus what a class-like entity owns.
max_elements_per_entityMembers of a class-like entity; parameters of a callable.
max_doors_per_folderFiles outside the folder land on, ties included — how many front doors it really has.
max_importers_per_fileOther files depending on this one, counted against the file that declares what they use rather than the one they named.

A tool may hand you a diagnosis you are free to disagree with; it may not fail your build with one.

ADR 0024 — a-check-fails-on-the-projects-rules-not-mezzs

This is why min_shape: fractal is not a rule anybody can declare, and its absence is a decision rather than an omission. The tiers are Mezzanine's, and three separate decision records have each conceded that its gates can be wrong about a specific folder. Rules are expressed over Mezzanine's measurements — but the threshold is arbitrary, arguable, and the project's to argue over.

What it looks like when it holds

Four rules, and a door in every language.

The ladder measures; it does not tell you how to write. These four rules are what the engine is developed against — held to across seven languages, in seven copies of the same small program, every folder of every one of them scoring fractal. They are language-independent. What changes between them is only which file the convention makes the door.

Rust the door is mod.rs
TypeScript the door is index.ts
Python the door is __init__.py
Java the door is the one public class
Kotlin the door is the file that isn't internal
Dart the door is <folder>.dart
Go the door is the one file with exports

The rules themselves

  1. A folder has one door. One file is the entry point; everything outside the folder enters through it.
  2. A file takes its dependencies from its parent. The door imports its own children. Nothing else in the folder imports anything in it.
  3. At most seven entities per file. Everything a reader meets on opening it.
  4. At most seven elements per entity. Fields, variants, methods — the same span, one level down.

The first two make the picture a tree; the last two keep each node small enough that the tree is worth drawing. In practice rule 2 is the one with teeth, and it always lands in the same place: two sibling subtrees want to share a type. The answer is never to let one import the other — it is for the parent to translate, which costs about four lines and buys a drawing that branches instead of merging.

Structure is the half a map cannot supply.

The graph tells you what the code is. The ladder tells you whether it can be read — and names the one move that makes it more readable than it was.