Duplicate and shared code¶
Code that exists twice has to be read, tested, secured and fixed twice, and the copies drift apart. CodeSynapse finds it inside one repository and across the repositories you can see, and recommends where a shared library or framework would pay off.
What counts as a duplicate¶
Each function or method of at least 6 lines and 5 statements gets a structural fingerprint at ingest (Node.CloneHash). For Go it is computed from the type-checked syntax tree. Python, JavaScript, TypeScript and Vue have no type information, so their fingerprint comes from tokens: comments, docstrings, strings and numbers are dropped, called functions and member names are kept, and every other identifier counts as a variable. They can therefore also match code that differs only in what it reads. The table describes Go:
| Abstracted (copies still match) | Kept (copies must agree) |
|---|---|
| Names of parameters and local variables | Functions and methods called |
| Literal values (only their kind is kept) | Types, struct fields, imported packages |
| Comments and formatting | Operators and control flow |
| The receiver's type | Statement structure and order |
Tests and generated files are skipped. No source is stored: only the hash, the location and the size.
What it does not find
Heavily rewritten copies (below about 70% alike) are not matched, and a function written as function f never matches the same code written as an arrow function. Python never matches JavaScript or TypeScript; JavaScript, TypeScript and Vue can match each other. Small functions (getters, one-line wrappers) are ignored on purpose.
Edited copies¶
Copies that were edited after copying (a line added, a condition changed) have a different structural fingerprint, so exact matching misses them. Each fingerprinted function also gets a MinHash signature of its normalised tokens (Node.CloneSig): 32 values of 16 bits over overlapping groups of three tokens. Two functions whose signatures agree on at least 70% of the values count as edited copies. Locality-sensitive hashing picks the pairs to compare, so large repositories stay fast.
- Only functions of at least 10 lines are compared, and a function less than half the size of another is never its edited copy.
- The largest variant leads a group and the others join only if they are close to it. Joining through a chain of neighbours would glue together functions that just share boilerplate.
- A near group lists every copy of every variant. Its redundant lines are the lines of the differing variants, each counted once, because copies inside one variant belong to an exact group. Exact and near lines are reported side by side and never added twice.
- The similarity is an estimate (about plus or minus 8 points), shown as "~88% alike".
In practice this surfaces code that was copied and drifted, for example three API handlers that read, check and write a report the same way, or two HTTP clients with the same request, retry and decode steps. Look at what differs before merging: sometimes the difference is the reason the copies exist.
Inside one repository¶
flowchart LR
ingest[Ingest: fingerprint every function] --> group[Group by fingerprint]
group --> scope[Scope: file, package or repository]
scope --> rank[Rank by redundant lines]
rank --> report[(Repository.duplicates)]
A group lists every copy with file and lines. Redundant lines are the lines left when one copy is kept. The scope says how to fix it:
- file: extract a helper in the same file;
- package: extract a shared function;
- repository: the copies are in different packages, so move one maintained copy into a package both import.
Where to look: the Duplicates tab under Code health on each repository, the duplicate_code MCP tool, or codesynapse duplicates ..
Across repositories¶
Each repository's report keeps the fingerprints of its largest candidates (up to 2,000). When you open Shared code, the API compares the reports of the repositories you may see; nothing else is read, so one organisation never sees another's code.
flowchart TB
a[(Repo A report)] --> x[Join on fingerprint]
b[(Repo B report)] --> x
c[(Repo C report)] --> x
x --> g[Shared groups: two or more repositories]
g --> s{Same set of repositories<br/>keeps copying?}
s -- 3 groups or 50+ redundant lines --> rec[Recommendation]
rec --> lib[library]
rec --> fw[framework: 3+ areas]
Exact and edited groups found in the same set of repositories are bundled into one recommendation:
- library: a shared module the repositories import, named after the most common package;
- framework: the same, when the copies span three or more areas (for example auth, logging, config and HTTP), which means the repositories rebuild the same foundation.
The recommendation lists candidate functions, the repositories involved and the lines saved. It is a heuristic starting point: check that the copies really mean the same thing before extracting them.
A repository whose report hit the cap is listed as partial, because copies of its smaller functions may be missing.
Where it shows up¶
| Surface | Use |
|---|---|
| Dashboard, repository, Code health, Duplicates | Groups, scope and locations for one repository |
| Overview, Code quality | A low-priority item per repository that repeats 5% or more of its function lines (and at least 100 lines), and up to three recommendations to share code between repositories |
| Dashboard, Shared code | Cross-repository groups and recommendations |
Impact of a change, get_pr_context, codesynapse review |
Changed functions that have exact or edited copies, with where the copies are and a checklist item. get_pr_context and the web page use the last ingest, so a function added by the change appears only after a Sync; codesynapse review --offline indexes the working tree and sees new copies |
Symbol explorer, get_symbol_context |
A "Duplicate code" panel / duplicate field on any function that has exact or edited copies, with where the others are |
MCP duplicate_code, shared_code |
Agents check before writing a helper or copying code |
CLI codesynapse duplicates [path...] |
One path for one repository; several paths also compare them |
GET /api/v1/repos/{repo}/duplicates, GET /api/v1/shared-code |
JSON for both |
Re-ingest (Sync) existing repositories once to create the first report.
Failing a CI job¶
codesynapse duplicates . --max-percent 5 prints the report and exits with an error when a repository repeats more than 5% of its function lines (exact and edited copies together). With several paths, each repository is checked on its own. Without the flag the command never fails. Pick a limit just above today's figure and lower it as you clean up, so new copies are caught without blocking old debt:
- run: codesynapse duplicates . --max-percent 5 --limit 10
How to merge a group¶
Each group on the Duplicates page has How to merge; the Plan page links its merge steps there, and the refactor_duplicates MCP tool answers the same for an agent (pass the hash and kind that duplicate_code lists). It works from the graph only and never edits code. For one group it gives:
- A name and a home for the shared function: the name every copy shares, or the most common one (alphabetical on a tie), made exported if any copy is public; the home is the same file, the same package, or the closest directory the copies share (a new package when they share none).
- The approach: extract one function and make the copies call it; for methods of different types, a function that takes what the methods use from their receivers; for edited copies, list the differences first and turn each into a parameter or a thin wrapper.
- The copies with their callers (what to update) and the tests that reach them.
- Risks worked out from the graph: public API, copies in different packages (import cycles), signatures that differ beyond names, copies no test reaches, copies nothing calls (delete rather than merge), copies that touch sensitive APIs, and for edited copies that a difference may be the point.
- Steps in order: confirm, pin behaviour with tests, create the function, point the copies at it, run the build and tests, delete the copies, Sync.
GET /api/v1/repos/{repo}/duplicates/refactor?hash=…&kind=exact|near returns the same as JSON.