Skip to content

Repository files navigation

zodchiy

An architectural audit where a finding is admissible only once its cost has been measured. Not a linter: the cost is not in the import graph, it is in the git history, and here it is the admission rule rather than a decoration.

evals python deps license

How a finding is admitted

Every architecture linter can tell you that a module has fan-in 40 or that there is an import cycle. None can tell you what it costs. A finding that cannot be priced does not enter the report — that single rule is what makes the output short enough to act on.

The method itself is one page: DOCTRINE.md — what the audit requires at each step, the risk catalogue, the admission rule, and what the tool does when it cannot deliver.

The name is Russian: зодчий, a master builder.

What a run looks like

Step one is a script. No model, no dependencies, and it is the whole measurement — everything after it is judgement built on top:

git clone --filter=blob:none https://github.com/Textualize/rich /tmp/rich
cd /tmp/rich && git checkout 9d8f9a372cc5
python3 zodchiy.py measure /tmp/rich --since "20 years ago" --out measure.json
{
  "calibration_passed": true,
  "blocked_metrics": [],
  "confidence_ceiling": "verdict",
  "commits_with_code": 1942,
  "files": 213,
  "edges": 750,
  "runtime_cycles": 1,
  "type_only_cycles": 1,
  "containment": 0.672,
  "coupling_pairs": 289
}

Read the first three fields before the rest. calibration_passed false means the history is too thin to price anything, blocked_metrics names what could not be measured, and confidence_ceiling is the strongest verdict this run is allowed to reach. On a young repository the tool refuses most of its own metrics rather than reporting weak ones — that refusal is the product.

Then the doctrine turns the measurement into findings. Here is one, taken verbatim from the ledger of that run rather than retold:

Finding console.py concentrates the widest fan-in and the highest rework rate in the package
Risk R1, priority 1, confidence finding
Symptom 2699 LOC, 116 functions, max nesting 11, fan-in 110 — the 100th percentile of this repository. 463 edits, more than any other file, and a Wilson lower bound on the rework rate of 0.50: half of the changes to this file come back as a fix within days, against a repository-wide 0.263.
Remedy Extract the render pipeline (segment assembly and the render_* helpers) into its own module and leave Console a facade that delegates. An extraction with the public API unchanged, not a redesign.
Predicted gain Half of the change traffic to console.py stops returning as a fix within the week.
Falsifiable as behavior.stability.unstable_files[file=rich/console.py].rework_rate_lb <= 0.40 in the next measurement

Where every one of those numbers came from:

  structure.hubs[file=rich/console.py].fan_in
  structure.complex_files[file=rich/console.py].max_nesting
  behavior.hotspots[file=rich/console.py].edits
  behavior.stability.unstable_files[file=rich/console.py].rework_rate_lb
  behavior.stability.rework_rate

examples/rich/ is that run in full — measurement, ledger, twelve refutation verdicts, report, SARIF. Read report.md first: the largest number in that measurement, a 50-file dependency cycle, is the one thing the report refuses to turn into a recommendation, because its cost could not be established.

Install

The canonical form is an agent skill. ./install.sh finds the harnesses present on the machine and refuses to overwrite anything it did not write.

git clone https://github.com/Socialpranker/zodchiy && cd zodchiy && ./install.sh

Python 3.11+ and git. Nothing outside the standard library, by design: a tool that audits other people's repositories should not add a supply chain to do it.

python3 zodchiy.py snapshot .zodchiy/measure.json --out .zodchiy/baseline.json
python3 zodchiy.py gate .zodchiy/measure.json --baseline .zodchiy/baseline.json   # exit 1 on regression
python3 zodchiy.py export --findings .zodchiy/findings.csv --measure .zodchiy/measure.json --format sarif

Three axes

Axis Source Blind to
Intent ADR, README, agent instructions, import-linter / ArchUnit / eslint-boundaries lies once the docs fall behind the code
Structure scripts/structure.py — import graph, cycles, fan-in/out, complexity DI, registries, reflection, string keys
Behaviour scripts/behavior.py — churn, fix share, co-change, ownership code that has not changed yet

A structural smell nobody ever pays for is not a finding here. Neither is a painful file with no structural cause — that one is a staffing problem.

What is verified, and what is not

The rule this tool applies to other people's code applies to its own claims.

Verified.

  • 171 unit tests, green
  • 14 frozen end-to-end cases against ground truth, each dirty case paired with a clean one — without the pair, "found N" cannot be told apart from "found N plus ten false ones" (method borrowed from OWASP Benchmark)
  • degenerate repositories: no commits, one commit, non-UTF-8 file, detached HEAD, not a git repository at all. Each must answer with an explicit field, not a traceback
  • the test harness itself, by mutation: it goes red when it should
  • the one live run over a foreign repository is held as a regression, not kept as an illustration: paths resolve, every finding passed step 4 and reached both the report and the exports
  • the skill's own description, against a catalogue of twelve competing tools over 20 cases, half of them phrased without the words "architecture" or "tech debt". Split train/test, edited on train only. After the edit: test recall 0.83, false positives 0.25 — the price of widening it, stated rather than hidden

Not verified, stated plainly because a tool that hides its own gaps has no business auditing yours.

  • no adapter has ever been run inside its target harness. Built is not working
  • the trigger evaluation ran once; variance between repeats is unmeasured. Its off-domain control failed outright: swap the description for one about code formatting and three of four architectural requests still land here. The floor is set by the catalogue and by the skill's name — what the description demonstrably moves is 6 cases out of 20
  • step 4, the refutation pass, has never run end to end with independent lenses — only the mechanics of recording its verdict are covered
  • recon mode has never been run
  • thresholds were calibrated on two repositories and will drift on a third. Percentiles travel between projects; absolute numbers do not — CALIBRATION.md

How the audit runs

Six steps. The first is a script; the rest are a doctrine an agent executes, and each has something mechanical holding it honest.

  1. Measure. Cycles (type-only ones kept apart — they exist to break cycles), hubs and fan-in through barrels, per-function complexity, temporal coupling, hotspots by fix share, change spread, rework within days, ownership. Every absolute number arrives with its percentile, and the calibration — blocked metrics, regex fallbacks, history window — is part of the output rather than a footnote.
  2. Map, without verdicts. The tops of the rankings, never the tree of directory names; every claim marked OBSERVED, INFERRED or UNKNOWN. A verdict issued before the map drags the whole analysis behind it.
  3. Judge. A risk catalogue (R1–R6) behind a materiality gate, Pain × Spread. Each entry carries its own what not to flag: a composition root with high fan-out is a design, not a mess.
  4. Refute. Every high-priority finding is attacked through four lenses. What survives is marked as surviving; what does not is dropped, not softened. Without subagents the lenses run sequentially — and then confidence is capped and the degradation is printed in the report.
  5. Report. Opens with Checklist: X/Y, closes with blind spots assembled mechanically: blocked metrics, regex-parsed files, the history window, links no import graph can see, and how step 4 actually ran. Silently truncating a top-N list is forbidden — say how many were dropped.
  6. Artefacts. Current-state document, ADRs, migration plan, and a findings ledger the next run compares itself against.

Three modes: audit (your own repository), plan (findings into decisions and their order), recon (a repository you have just been handed).

Every finding must predict a gain, and verify compares that prediction against the next measurement. An audit nobody can be wrong about is not an audit.

The hard rule

Numbers come from a script. Judgement comes from the model. Never the reverse.
No metric is eyeballed — not fan-in, not complexity, not a cycle.
No finding is issued before the full map is built.

Prose cannot be told apart from a number recalled from memory; a path can be checked, and zodchiy.py selfcheck refuses a report whose numbers do not resolve.

What is where

Path What it is
SKILL.md the doctrine itself — the audit an agent executes. Russian, 23 KiB
DOCTRINE.md the same method in one English page
zodchiy.py the CLI: measure, snapshot, diff, gate, add, refute, selfcheck, verify, export
scripts/ the measurement — structure.py (graph), behavior.py (history), ledger.py (findings, gate, export)
references/ what the doctrine consults: risk catalogue, materiality gate, axis disagreement, refutation lenses, remedies, artefact templates
examples/rich/ one complete run over a foreign repository, held as a regression
evals/ the regression suite — unit, frozen end-to-end, degenerate repositories, the live run, the description eval
schema/, dist/ findings JSON schema; adapters generated from SKILL.md
SPEC.md why each rule is what it is — sources, calibration decisions, rejected alternatives

Adapters

One text under four names — each harness looks for a different filename.

File Goes to
AGENTS.md Codex (~/.codex/AGENTS.md, or the repository root), Grok Build, Cursor, Zed
GEMINI.md Gemini CLI (~/.gemini/GEMINI.md, or the repository root)
QWEN.md Qwen Code (~/.qwen/QWEN.md)
IFLOW.md iFlow CLI (~/.iflow/IFLOW.md)
Slash command Goes to
gemini/commands/zodchiy.toml ~/.gemini/commands/zodchiy.toml
iflow/commands/zodchiy.toml ~/.iflow/commands/zodchiy.toml

Claude Code and Grok Build need no adapter — the whole directory goes to ~/.claude/skills/zodchiy/ or ~/.grok/skills/zodchiy/. Adapters are generated from SKILL.md by scripts/build_adapters.py; do not edit dist/ by hand.

Language

The doctrine — SKILL.md, SPEC.md and references/ — is in Russian. It is read by a model, and models read Russian. Translating 280 KiB would create a second copy that drifts from the first within a month, which is the exact failure mode this repository exists to detect. English is the interface: README, installer, CLI help of the public documents.

License

MIT. See LICENSE.

About

An architectural audit where a finding is admissible only once its cost is measured from git history. A doctrine for coding agents plus a stdlib-only measurement CLI.

Topics

Resources

Stars

23 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages