fensu dupes reports concrete duplicated code: ranked clusters of exact, renamed,
and near-miss function-level copies in Python, Rust, TypeScript, JavaScript, and
Svelte. It is an advisory review aid, not a gate. Where fensu check
enforces the policy, dupes points at copies worth a second look. For why
duplication counts as architectural drift, and a worked example, see
Duplicated code.
fensu dupes never fails on findings. It exits 0 whenever analysis succeeds,
whatever it finds. Unlike fensu check, its findings never need to reach zero:
treat each cluster as a hypothesis to review.Usage
Run it from anywhere under a configured project to see the top 30 clusters:fensu map, dupes needs Fensu
configuration. Without a fensu.toml or
[tool.fensu] table it exits 2.
The most useful everyday forms are:
Reading the report
The first line is the summary, and the second counts the analysed units per language and what configuration hid. Each numbered cluster then shows:- Category: the cluster’s weakest link.
exactmeans identical tokens,renamedmeans only names and literals differ, andnear-missmeans similar but not identical. - Similarity (
sim): one value, or amin-maxrange when the links differ. - Duplicated tokens: the estimated tokens that would disappear if only one copy remained. Clusters are ranked by this number.
- Members: one line per copy as
path:start-end name (tokens). Methods are qualified asClass.method.
[changed]: the member touches lines changed since the--sincerevision.[forced]: the member implements a contract method that configuration requires every subclass to define itself. See contract exemptions.
--json for the
rest. When more clusters exist than --top allows, a final
... N more clusters (use --top) line says so. The closing
fensu dupes: found ... line is written to stderr, so redirected stdout stays
clean.
Options
Changes since a revision
--since REV keeps only clusters with at least one member touching lines added
or changed since REV, including uncommitted and untracked files. Run it before
review to see only the duplication your branch introduces or touches:
2:
Showing divergence
--diff adds up to 12 differing lines between the first two members of each
cluster. When a cluster has at least two visible (non-forced) members, those are
the two compared. Combined with --since, it shows whether a change reached only
one copy:
orders/summary.py only. The billing and
reports copies still count cancelled orders. Identical members report
no differing lines.
Narrowing the report
--path keeps clusters with at least one member matching a glob, so copies
elsewhere in the repository still show up as members. Use it before adding
helpers to an area to find an existing owner:
--lang restricts analysis to the named languages, and --min-tokens and
--min-similarity tighten or loosen detection. --min-similarity must be
greater than 0 and at most 1; other values are rejected with exit code 2.
JSON output
--json prints the same report as deterministic JSON on stdout:
total_clusters counts every matching cluster, while clusters holds at most
--top entries. links connect member indexes, and diff appears only with
--diff.
What is analysed
Sources come from every configured target, discovered the same wayfensu check discovers them: target roots and tooling, with generated paths and
evaluation targeting applied.
Configured test paths, colocated web tests, Rust tests/ and benches/
directories, and Rust #[cfg(test)] and #[test] items are skipped unless you
pass --include-tests. Paths matching [dupes].exclude are
always skipped.
Units are functions:
- Python: top-level functions and class methods (nested classes as
Outer.Inner.method). Decorators are not part of the unit. - Rust:
fnitems with bodies, including impl and trait methods (Type::method). - TypeScript and JavaScript: function declarations, functions assigned to a
const, and class methods and function-valued properties (Class.method). - Svelte: the same units inside every
<script>block, reported with component line numbers.
How similarity works
Each unit becomes a normalised token stream. Local names, parameters, attribute reads, and literals become placeholders. Keywords, operators, and Python layout stay. Call targets keep their names, so two functions with the same shape that call different helpers are near-misses rather than renamed copies. Comments, docstrings, and type annotations are dropped.exact: identical tokens after dropping comments, docstrings, and annotations.renamed: identical normalised streams, so only names and literals differ.near-miss: similarity at or above--min-similarity. When the smaller unit has fewer than 80 tokens, a near-miss needs at least0.9.
--min-tokens are ignored. Similar pairs join into transitive
clusters, so one cluster can hold members of different categories. Fragments
shared by very many units are treated as boilerplate and do not produce
candidates.
Configuration
[dupes] sits at the top level of fensu.toml, beside [targets] in a
multi-target configuration, or under [tool.fensu.dupes] in pyproject.toml. It
does not affect fensu check. Globs use Fensu’s path syntax: * stays within
one segment, ** crosses segments, and a pattern without / matches a name at
any depth.
Unknown keys are rejected. Every
allowlist and contract_exemptions entry
needs a non-empty paths list and a non-empty reason. A missing reason is a
configuration error and exits 2:
--min-similarity and
--min-tokens on the command line instead.
Allowlist
An allowlist entry hides a pair when both members match the entry’spaths.
Hidden pairs are counted in the summary, and clusters left with no visible link
disappear:
Contract exemptions
Some contracts require every implementation to define a method itself, which produces copies that must stay. A contract exemption marks those methods[forced]:
The contract methods are the contract class’s abstract methods (
@abstractmethod
or @abc.abstractmethod, including inherited ones that are not overridden), read
statically from the source. A method is forced when its top-level class is under
paths, derives from the contract, and would otherwise inherit that method from
the contract, from a forbidden_owners class, or from nowhere. If any class in
the method resolution order cannot be resolved inside the repository, the method
is not forced. External and standard-library bases such as ABC or Generic[T]
never block the exemption.
Links between two forced members are hidden and counted as contract-exempt
members. Links from a forced member to any other unit stay visible, because a
private helper copy is still worth reviewing. Forced members are listed last and
add nothing to the duplicated-token estimate:
forbidden_owners class that is not found in the analysed
source, is a configuration error. Contract exemptions currently apply to
Python classes only.
Acting on findings
A genuine duplicate is evidence, not only a cleanup task. Two copies of one behaviour often point to wider drift: a missing shared owner, two subsystems implementing one concept, logic on the wrong side of a boundary, or copies that have already diverged.- Check for divergence first. Run
--diffto see whether a fix reached only one copy. A difference found only in one copy may be a bug in the others, as in the cancelled-order example above. Look at nearby clusters in the same modules for a larger pattern. - Record the diagnosis before consolidating. Consolidation removes the only deterministic signal of the underlying drift, and the wider problem is much harder to find once the duplicate is gone. Note why the copy existed in the review, handoff, or an issue, and fix the root cause when it is in scope.
- Consolidate duplication you introduce or touch. Run
fensu dupes --since origin/mainbefore review, and merge genuine copies into one shared owner. Report unrelated duplication rather than refactoring it as part of an unrelated change. - Allowlist intentional mirrors with a reason. When copies must stay, record
them in
[dupes].allowlistor as a contract exemption instead of forcing a merge.
fensu dupes --since origin/main before review and treat findings the same way.
Exit codes
Related
Duplicated code
Why copies are drift, with a worked consolidation example.
fensu check
The blocking counterpart that enforces the configured policy.
Configuration
Targets, scopes, and evaluation targeting that dupes shares.
fensu map
Trace call flow before consolidating copies.
fensu skills
Generated guidance that includes the dupes workflow.

