fensu check enforces rules about where code lives and how it is shaped. It has
no rule for “this logic already exists somewhere else”, because no rule can say
in advance which two functions ought to be one. Duplication still counts as
architectural drift, though. It is simply drift that shows up as evidence rather
than as a rule violation.
fensu dupes collects that evidence. It reports ranked clusters of
function-level copies, so you can decide what each one means.
Why a copy is drift
A copied function is rarely just repeated text. It usually points at a structural problem:- A missing shared owner. Several modules needed the same behaviour, and none of them owned it, so each grew its own copy.
- Parallel implementations of one concept. Two subsystems each implement “what counts as available stock” or “how an order total is computed”.
- Logic on the wrong side of a boundary. Behaviour that belongs to one domain was re-implemented in its callers instead of being published by its owner.
fensu check still reports Found 0 faults, because
every copy sits in a valid place with a valid shape.
Evidence worth keeping
Similarity is deterministic. The same source always yields the same clusters, so a cluster is a concrete, reproducible claim you can review. A cluster is also usually the only trace of the underlying drift. Once you merge the copies, the missing owner, the misplaced logic, or the fix that reached only one copy stops being visible anywhere. Diagnose before you consolidate:- Work out why the copy exists.
- Use
--diffto see which differences are deliberate and which are an unfinished fix. - Record the diagnosis in the review, handoff, or an issue.
- Then remove the copies.
A worked example
A small shop publishes its product catalogue in three feed formats. Each feed is its own subdomain underfeeds/, and each copied the same row-preparation helper
into its own _helpers/rows.py. The JSON feed genuinely differs: it publishes
prices in cents, while the others publish decimal prices.
A fix that reached one copy
A branch stops exporting discontinued products. The developer found the CSV helper and fixed it there:fensu check still passes. Before review, fensu dupes compares the branch with
main:
- Line 12 is the fix. It reached the CSV feed only, so the JSON and TSV feeds would keep publishing discontinued products.
- Line 15 is a genuine difference: the JSON feed’s price format.
The diagnosis
The feeds share one concept, “the rows a feed publishes”, and nothing owns it. The three copies are the symptom, and the partial fix is the cost. Write that down before touching the code, for example:Consolidating into a shared owner
The obvious shortcut is to keep the CSV copy and import it from the other feeds.fensu check rejects that, because it reaches into another owner’s internals:
feeds/rows/, and the real
difference becomes an explicit keyword argument rather than a divergent copy:
check shaped that placement too. The first draft named the module
prepare_rows.py, and check returned two faults:
FFS120asked for keyword-only parameters, so every call names its meaning.FFL105reported that a publicmain/entry imported only from inside its ownfeedsdomain should carry a_prefix until another domain needs it.
What is left
Runningdupes again on the area surfaces one more pair:
renamed means. Here the team decides that each published format owns its
entry point, so formats can change independently. That makes the pair an
intentional mirror, recorded with its reason:
How dupes and check fit together
The two commands split the work:fensu dupesfinds candidates. It tells you that behaviour exists more than once and where the copies disagree.fensu checkconstrains the fix. Its layer and role rules decide where the new shared owner can live and how other owners may import it, as theFFL101andFFL105faults above show.
check cannot see that two valid functions are one
concept. dupes has no opinion about where the consolidated owner belongs.
Intentional duplication
Not every copy should be merged. Fensu gives you two explicit, reviewable ways to keep one. Both require areason and live in fensu.toml, never in source
comments.
Mirrors
Some code intentionally mirrors something else: one module per published format, per external specification, or per independently versioned contract. Record the mirror in[dupes].allowlist so the cluster stops
reappearing, and so the reason is on record for the next reader.
Contract-forced copies
Some interfaces require every implementation to define a method itself. For example, a contract test may check that each exporter implementsexport
directly rather than inheriting it. Those copies are required, and a
contract exemption marks them [forced] and
hides the links between them.
The exemption covers only the forced methods. A private helper copied behind
them is still ordinary duplication and should still be shared:
export methods are hidden as contract-exempt. The _render_lines
helper each of them calls is still reported, because nothing requires that
helper to be copied.
What it is not
- Not a gate.
fensu dupesexits0whenever analysis succeeds. It never fails CI on findings. - Not a zero target. Findings never need to reach zero. A cluster is a hypothesis, and some clusters are correct as they are.
- Not a style metric. There is no duplication score or percentage. Clusters are ranked by estimated duplicated tokens, only to put the largest copies first.
- Function-level only. Units are functions and methods. Module-level code is not compared, and a block copied into two otherwise different functions appears only if the whole functions are similar enough.
What the categories mean
Similarity (
sim) is a token-level match ratio from 0 to 1. Local names,
parameters, attribute reads, and literals are normalised away. Called function
names are kept, so two functions with the same shape that call different helpers
count as near-misses rather than renamed copies.
Known limits
- Units with fewer than 60 normalised tokens are ignored (
--min-tokens). A near-miss needs similarity0.8or higher (--min-similarity), and at least0.9when the smaller unit has fewer than 80 tokens. - Supported languages are Python, Rust, TypeScript, JavaScript, and Svelte. TypeScript, JavaScript, and Svelte are compared with each other. Python and Rust are compared only within their own language.
- Test code is skipped unless you pass
--include-tests. - Fragments shared by very many units are treated as boilerplate and do not produce candidates.
- Contract exemptions apply to Python classes only.
fensu dupes for every option and configuration key.
With coding agents
Agents copy code readily. When a model needs behaviour that already exists somewhere it has not read, writing it again is the easy path. Generated agent skills include duplicated-code guidance unconditionally, so every agent working in the repository follows the same loop:- Before adding helpers or logic to an area, run
fensu dupes --path '<area glob>'and reuse the existing owner. On the feeds before consolidation,--path 'src/shop/feeds/json/**'would have shown all threeprepare_rowscopies. - Before review, run
fensu dupes --since origin/mainand look at every cluster marked[changed], with--difffor divergence. - Diagnose and report why a genuine copy exists before consolidating it, and keep consolidation within the requested scope.
- Allowlist intentional mirrors with a reason instead of forcing a merge.
- Run
fensu checkso the consolidated owner lands in the right place.
Related
fensu dupes
Every option, output format, and
[dupes] configuration key.Adopting Fensu
Introducing dupes to an existing repository.
Architecture model
The owners and boundaries a consolidated function must respect.
fensu skills
Generated guidance that carries this workflow to agents.

